From 6319933ed0d2dfbd086dd99e2cacea207e8f91f7 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 22 Sep 2016 21:15:37 -0300 Subject: [PATCH 001/102] Concrete class prototype --- Discord.Net.sln | 36 +- .../API/CDN.cs | 2 +- .../API/Common/Application.cs | 0 .../API/Common/Attachment.cs | 0 .../API/Common/Ban.cs | 0 .../API/Common/Channel.cs | 0 .../API/Common/Connection.cs | 0 .../API/Common/Embed.cs | 0 .../API/Common/EmbedProvider.cs | 0 .../API/Common/EmbedThumbnail.cs | 0 .../API/Common/Emoji.cs | 0 .../API/Common/Game.cs | 0 .../API/Common/Guild.cs | 0 .../API/Common/GuildEmbed.cs | 0 .../API/Common/GuildMember.cs | 0 .../API/Common/Integration.cs | 0 .../API/Common/IntegrationAccount.cs | 0 .../API/Common/Invite.cs | 0 .../API/Common/InviteChannel.cs | 0 .../API/Common/InviteGuild.cs | 0 .../API/Common/InviteMetadata.cs | 0 .../API/Common/Message.cs | 0 .../API/Common/Overwrite.cs | 0 .../API/Common/Presence.cs | 0 .../API/Common/ReadState.cs | 0 .../API/Common/Relationship.cs | 0 .../API/Common/RelationshipType.cs | 0 .../API/Common/Role.cs | 0 .../API/Common/User.cs | 0 .../API/Common/UserGuild.cs | 0 .../API/Common/VoiceRegion.cs | 0 .../API/Common/VoiceState.cs | 0 .../API/DiscordRestApiClient.cs | 511 +++++++----------- .../API/Image.cs | 2 +- .../API/Int53Attribute.cs | 0 .../API/Rest/CreateChannelInviteParams.cs | 16 + .../API/Rest/CreateDMChannelParams.cs | 9 +- .../API/Rest/CreateGuildBanParams.cs | 3 +- .../API/Rest/CreateGuildChannelParams.cs | 23 + .../API/Rest/CreateGuildIntegrationParams.cs | 11 +- .../API/Rest/CreateGuildParams.cs | 15 +- .../API/Rest/CreateMessageParams.cs | 22 + .../API/Rest/DeleteMessagesParams.cs | 17 + .../API/Rest/GetChannelMessagesParams.cs | 10 + .../API/Rest/GetGatewayResponse.cs | 0 .../API/Rest/GetGuildMembersParams.cs | 9 + .../API/Rest/GetGuildPruneCountResponse.cs | 0 .../API/Rest/GuildPruneParams.cs | 7 +- .../Rest/ModifyChannelPermissionsParams.cs | 13 +- .../API/Rest/ModifyCurrentUserNickParams.cs | 7 +- .../API/Rest/ModifyCurrentUserParams.cs | 8 +- .../API/Rest/ModifyGuildChannelParams.cs | 7 +- .../API/Rest/ModifyGuildChannelsParams.cs | 11 +- .../API/Rest/ModifyGuildEmbedParams.cs | 14 + .../API/Rest/ModifyGuildIntegrationParams.cs | 16 + .../API/Rest/ModifyGuildMemberParams.cs | 20 + .../API/Rest/ModifyGuildParams.cs | 30 + .../API/Rest/ModifyGuildRoleParams.cs | 20 + .../API/Rest/ModifyGuildRolesParams.cs | 7 +- .../API/Rest/ModifyMessageParams.cs | 3 +- .../API/Rest/ModifyPresenceParams.cs | 9 + .../API/Rest/ModifyTextChannelParams.cs | 3 +- .../API/Rest/ModifyVoiceChannelParams.cs | 7 +- .../API/Rest/UploadFileParams.cs | 35 ++ .../API/WebSocketMessage.cs | 0 .../Audio/IAudioClient.cs | 8 +- .../Audio/Opus/OpusApplication.cs | 0 .../ConnectionState.cs | 0 src/Discord.Net.Core/Discord.Net.Core.xproj | 19 + .../DiscordConfig.cs | 0 .../Entities/Channels/ChannelType.cs | 0 .../Entities/Channels/IChannel.cs | 8 +- .../Entities/Channels/IDMChannel.cs | 0 .../Entities/Channels/IGroupChannel.cs | 16 + .../Entities/Channels/IGuildChannel.cs | 8 +- .../Entities/Channels/IMessageChannel.cs | 10 +- .../Entities/Channels/IPrivateChannel.cs | 0 .../Entities/Channels/ITextChannel.cs | 0 .../Entities/Channels/IVoiceChannel.cs | 0 .../Guilds/DefaultMessageNotifications.cs | 0 src/Discord.Net.Core/Entities/Guilds/Emoji.cs | 33 ++ src/Discord.Net.Core/Entities/Guilds/IBan.cs | 13 + .../Entities/Guilds/IGuild.cs | 16 +- .../Entities/Guilds/IGuildIntegration.cs | 5 +- .../Entities/Guilds/IUserGuild.cs | 0 .../Entities/Guilds/IVoiceRegion.cs | 0 .../Entities/Guilds/IntegrationAccount.cs | 0 .../Entities/Guilds/MfaLevel.cs | 0 .../Entities/Guilds/VerificationLevel.cs | 0 .../Entities/IApplication.cs | 2 +- .../Entities/IDeletable.cs | 0 src/Discord.Net.Core/Entities/IEntity.cs | 15 + .../Entities/IMentionable.cs | 0 .../Entities/ISnowflakeEntity.cs | 6 + .../Entities/IUpdateable.cs | 0 .../Entities/Invites/IInvite.cs | 0 .../Entities/Invites/IInviteMetadata.cs | 0 .../Entities/Messages/Direction.cs | 0 .../Entities/Messages/EmbedProvider.cs | 6 +- .../Entities/Messages/EmbedThumbnail.cs | 11 +- .../Entities/Messages/IAttachment.cs | 0 .../Entities/Messages/IEmbed.cs | 4 +- .../Entities/Messages/IMessage.cs | 8 +- .../Entities/Messages/ISystemMessage.cs | 6 + .../Entities/Messages/IUserMessage.cs | 0 .../Mentions}/ChannelMentionHandling.cs | 0 .../Mentions}/EveryoneMentionHandling.cs | 0 .../Messages/Mentions}/RoleMentionHandling.cs | 0 .../Messages/Mentions}/UserMentionHandling.cs | 0 .../Entities/Messages/MessageType.cs | 0 .../Entities/Permissions/ChannelPermission.cs | 0 .../Permissions/ChannelPermissions.cs | 0 .../Entities/Permissions/GuildPermission.cs | 0 .../Entities/Permissions/GuildPermissions.cs | 0 .../Entities/Permissions/Overwrite.cs | 2 +- .../Permissions/OverwritePermissions.cs | 0 .../Entities/Permissions/PermValue.cs | 0 .../Entities/Permissions/PermissionTarget.cs | 0 .../Entities/Roles/Color.cs | 0 .../Entities/Roles/IRole.cs | 19 +- .../Entities/Users/Game.cs | 10 +- .../Entities/Users/IConnection.cs | 0 .../Entities/Users/IGroupUser.cs | 10 + .../Entities/Users/IGuildUser.cs | 15 +- .../Entities/Users/IPresence.cs | 2 +- .../Entities/Users/ISelfUser.cs | 2 +- .../Entities/Users/IUser.cs | 9 + .../Entities/Users/IVoiceState.cs | 0 .../Entities/Users/StreamType.cs | 0 .../Entities/Users/UserStatus.cs | 0 .../Extensions/DiscordClientExtensions.cs | 0 .../Extensions/GuildExtensions.cs | 0 .../Extensions/GuildUserExtensions.cs | 4 +- .../Extensions/SnowflakeEntityExtensions.cs | 10 + .../Format.cs | 0 .../IDiscordClient.cs | 5 +- .../Logging/LogMessage.cs | 0 .../Logging}/LogSeverity.cs | 0 .../LoginState.cs | 0 .../Net/Converters/DiscordContractResolver.cs | 0 .../Net/Converters/ImageConverter.cs | 0 .../Net/Converters/NullableUInt64Converter.cs | 0 .../Net/Converters/OptionalConverter.cs | 0 .../Converters/PermissionTargetConverter.cs | 0 .../Net/Converters/StringEntityConverter.cs | 0 .../Net/Converters/UInt64ArrayConverter.cs | 0 .../Net/Converters/UInt64Converter.cs | 0 .../Net/Converters/UInt64EntityConverter.cs | 0 .../Net/Converters/UserStatusConverter.cs | 0 .../Net/HttpException.cs | 0 .../Net/Queue/Definitions/BucketDefinition.cs | 0 .../Net/Queue/Definitions/BucketGroup.cs | 0 .../Net/Queue/Definitions/BucketTarget.cs | 0 .../Net/Queue/Definitions/ChannelBucket.cs | 0 .../Net/Queue/Definitions/GlobalBucket.cs | 0 .../Net/Queue/Definitions/GuildBucket.cs | 0 .../Net/Queue/IQueuedRequest.cs | 2 +- .../Net/Queue/RequestQueue.cs | 4 +- .../Net/Queue/RequestQueueBucket.cs | 0 .../Net/Queue/RestRequest.cs | 2 +- .../Net/Queue/WebSocketRequest.cs | 13 +- .../Net/RateLimitException.cs | 0 .../Net/Rest/DefaultRestClient.cs | 0 .../Net/Rest/IRestClient.cs | 0 .../Net/Rest/MultipartFile.cs | 0 .../Net/Rest/RestClientProvider.cs | 0 .../Net/RpcException.cs | 0 .../Net/WebSocketException.cs | 0 .../Net/WebSockets/DefaultWebSocketClient.cs} | 0 .../Net/WebSockets/IWebSocketClient.cs | 0 .../Net/WebSockets/WebSocketProvider.cs | 0 .../RequestOptions.cs | 0 .../TokenType.cs | 0 src/Discord.Net.Core/Utils/MentionUtils.cs | 85 +++ .../Utils}/Optional.cs | 0 src/Discord.Net.Core/project.json | 62 +++ src/Discord.Net.Rest/Discord.Net.Rest.xproj | 19 + .../DiscordRestClient.cs | 174 +++--- .../DiscordRestConfig.cs | 0 .../Entities/Channels/ChannelHelper.cs | 211 ++++++++ .../Entities/Channels/RestDMChannel.cs | 113 ++++ .../Entities/Channels/RestGroupChannel.cs | 125 +++++ .../Entities/Channels/RestGuildChannel.cs | 158 ++++++ .../Entities/Channels/RestTextChannel.cs | 98 ++++ .../Entities/Channels/RestVoiceChannel.cs | 49 ++ .../Entities/Guilds/GuildHelper.cs | 183 +++++++ .../Entities/Guilds/RestBan.cs | 27 + .../Entities/Guilds/RestGuild.cs | 212 ++++++++ .../Entities/Guilds/RestGuildEmbed.cs} | 10 +- .../Entities/Guilds/RestGuildIntegration.cs | 77 +++ .../Entities/Guilds/RestUserGuild.cs} | 22 +- .../Entities/Guilds/RestVoiceRegion.cs | 38 ++ .../Entities/Invites/InviteHelper.cs | 21 + .../Entities/Invites/RestInvite.cs} | 40 +- .../Entities/Invites/RestInviteMetadata.cs} | 29 +- .../Entities/Messages/MessageHelper.cs | 33 ++ .../Entities/Messages/RestAttachment.cs | 32 ++ .../Entities/Messages/RestEmbed.cs | 30 + .../Entities/Messages/RestMessage.cs | 61 +++ .../Entities/Messages/RestSystemMessage.cs | 28 + .../Entities/Messages/RestUserMessage.cs | 135 +++++ .../Entities/RestApplication.cs} | 31 +- src/Discord.Net.Rest/Entities/RestEntity.cs | 19 + .../Entities/Roles/RestRole.cs | 52 ++ .../Entities/Roles/RoleHelper.cs | 22 + .../Entities/Users/RestConnection.cs | 35 ++ .../Entities/Users/RestGroupUser.cs | 29 + .../Entities/Users/RestGuildUser.cs | 74 +++ .../Entities/Users/RestSelfUser.cs | 45 ++ .../Entities/Users/RestUser.cs | 51 ++ .../Entities/Users/UserHelper.cs | 53 ++ .../Properties/AssemblyInfo.cs | 19 + src/Discord.Net.Rest/project.json | 39 ++ .../API/DiscordRpcApiClient.cs} | 1 - .../API/Rpc/Application.cs | 0 .../API/Rpc/AuthenticateParams.cs | 0 .../API/Rpc/AuthenticateResponse.cs | 0 .../API/Rpc/AuthorizeParams.cs | 0 .../API/Rpc/AuthorizeResponse.cs | 0 .../API/Rpc/ChannelSubscriptionParams.cs | 0 .../API/Rpc/ErrorEvent.cs | 0 .../API/Rpc/GetChannelParams.cs | 0 .../API/Rpc/GetChannelsParams.cs | 0 .../API/Rpc/GetChannelsResponse.cs | 0 .../API/Rpc/GetGuildParams.cs | 0 .../API/Rpc/GetGuildsParams.cs | 0 .../API/Rpc/GetGuildsResponse.cs | 0 .../API/Rpc/GuildStatusEvent.cs | 0 .../API/Rpc/GuildSubscriptionParams.cs | 0 .../API/Rpc/MessageEvent.cs | 0 .../API/Rpc/ReadyEvent.cs | 0 .../API/Rpc/RpcChannel.cs | 0 .../API/Rpc/RpcConfig.cs | 0 .../API/Rpc/RpcGuild.cs | 0 .../API/Rpc/RpcMessage.cs | 0 .../API/Rpc/RpcUserGuild.cs | 0 .../API/Rpc/SelectVoiceChannelParams.cs | 0 .../API/Rpc/SetLocalVolumeParams.cs | 0 .../API/Rpc/SetLocalVolumeResponse.cs | 0 .../API/Rpc/SpeakingEvent.cs | 0 .../API/Rpc/SubscriptionResponse.cs | 0 .../API/Rpc/VoiceStateEvent.cs | 0 src/Discord.Net.Rpc/Discord.Net.Rpc.xproj | 21 + .../DiscordRpcClient.Events.cs | 0 .../DiscordRpcClient.cs | 0 .../DiscordRpcConfig.cs | 0 .../Entities/IRemoteUserGuild.cs | 0 .../Entities/RemoteUserGuild.cs | 6 +- .../Entities/RpcMessage.cs | 0 .../Properties/AssemblyInfo.cs | 19 + .../RpcChannelEvent.cs | 0 .../Rpc => Discord.Net.Rpc}/RpcGuildEvent.cs | 0 src/Discord.Net.Rpc/project.json | 20 + .../AsyncEvent.cs | 0 .../ConcurrentHashSet.cs | 0 .../DateTimeUtils.cs | 0 .../Discord.Net.Utils.projitems | 26 + .../Discord.Net.Utils.shproj | 13 + .../Extensions/CollectionExtensions.cs | 0 .../Extensions/Permissions.cs | 152 ++++++ .../TaskCompletionSourceExtensions.cs | 8 - .../Logging/LogManager.cs | 49 +- .../Logging/Logger.cs | 2 +- .../MentionsHelper.cs} | 133 +---- src/Discord.Net.Utils/Paging/Page.cs | 22 + src/Discord.Net.Utils/Paging/PageInfo.cs | 20 + .../Paging/PagedEnumerator.cs | 67 +++ .../Permissions.cs | 26 +- .../Preconditions.cs | 3 +- .../API/DiscordSocketApiClient.cs | 1 - .../API/DiscordVoiceApiClient.cs} | 0 .../API/Gateway/ExtendedGuild.cs | 0 .../API/Gateway/GatewayOpCode.cs | 0 .../API/Gateway/GuildBanEvent.cs | 0 .../API/Gateway/GuildEmojiUpdateEvent.cs | 0 .../API/Gateway/GuildMemberAddEvent.cs | 0 .../API/Gateway/GuildMemberRemoveEvent.cs | 0 .../API/Gateway/GuildMemberUpdateEvent.cs | 0 .../API/Gateway/GuildMembersChunkEvent.cs | 0 .../API/Gateway/GuildRoleCreateEvent.cs | 0 .../API/Gateway/GuildRoleDeleteEvent.cs | 0 .../API/Gateway/GuildRoleUpdateEvent.cs | 0 .../API/Gateway/GuildSyncEvent.cs | 0 .../API/Gateway/HelloEvent.cs | 0 .../API/Gateway/IdentifyParams.cs | 0 .../API/Gateway/MessageDeleteBulkEvent.cs | 0 .../API/Gateway/ReadyEvent.cs | 0 .../API/Gateway/RecipientEvent.cs | 0 .../API/Gateway/RequestMembersParams.cs | 0 .../API/Gateway/ResumeParams.cs | 0 .../API/Gateway/ResumedEvent.cs | 0 .../API/Gateway/StatusUpdateParams.cs | 0 .../API/Gateway/TypingStartEvent.cs | 0 .../API/Gateway/UpdateStatusParams.cs | 0 .../API/Gateway/VoiceServerUpdateEvent.cs | 0 .../API/Gateway/VoiceStateUpdateParams.cs | 0 .../API/Voice/IdentifyParams.cs | 0 .../API/Voice/ReadyEvent.cs | 0 .../API/Voice/SelectProtocolParams.cs | 0 .../API/Voice/SessionDescriptionEvent.cs | 0 .../API/Voice/SpeakingParams.cs | 0 .../API/Voice/UdpProtocolInfo.cs | 0 .../API/Voice/VoiceOpCode.cs | 0 .../Audio/AudioClient.cs | 0 .../Audio/AudioMode.cs | 0 .../Audio/Opus/OpusConverter.cs | 0 .../Audio/Opus/OpusCtl.cs | 0 .../Audio/Opus/OpusDecoder.cs | 0 .../Audio/Opus/OpusEncoder.cs | 0 .../Audio/Opus/OpusError.cs | 0 .../Audio/Sodium/SecretBox.cs | 0 .../Audio/Streams/OpusDecodeStream.cs | 0 .../Audio/Streams/OpusEncodeStream.cs | 0 .../Audio/Streams/RTPReadStream.cs | 0 .../Audio/Streams/RTPWriteStream.cs | 0 .../DataStore.cs | 0 .../Discord.Net.WebSocket.xproj | 21 + .../DiscordSocketClient.Events.cs | 0 .../DiscordSocketClient.cs | 2 +- .../DiscordSocketConfig.cs | 0 .../Entities/Channels/SocketDMChannel.cs | 7 +- .../Entities/Channels/SocketGroupChannel.cs | 8 +- .../Entities/Channels/SocketGuildChannel.cs} | 6 +- .../Entities/Channels/SocketTextChannel.cs | 0 .../Entities/Channels/SocketVoiceChannel.cs | 0 .../Entities/Guilds/SocketGuild.cs | 8 +- .../Guilds/SocketGuildIntegration.cs} | 11 +- .../Entities/Messages/ISocketMessage.cs | 2 +- .../Entities/Messages/SocketSystemMessage.cs | 0 .../Entities/Messages/SocketUserMessage.cs | 0 .../Entities/Users/ISocketUser.cs | 0 .../Entities/Users/Presence.cs | 0 .../Entities/Users/SocketDMUser.cs | 2 +- .../Entities/Users/SocketGlobalUser.cs | 4 +- .../Entities/Users/SocketGroupUser.cs | 0 .../Entities/Users/SocketGuildUser.cs | 2 +- .../Entities/Users/SocketSelfUser.cs | 0 .../Entities/Users/VoiceState.cs | 0 .../Entities/Utilities}/MessageCache.cs | 0 .../Entities/Utilities}/MessageManager.cs | 0 .../Properties/AssemblyInfo.cs | 19 + src/Discord.Net.WebSocket/project.json | 20 + .../API/Rest/CreateChannelInviteParams.cs | 21 - .../API/Rest/CreateGuildChannelParams.cs | 21 - .../API/Rest/CreateMessageParams.cs | 21 - .../API/Rest/DeleteMessagesParams.cs | 16 - .../API/Rest/GetChannelMessagesParams.cs | 14 - .../API/Rest/GetGuildMembersParams.cs | 12 - .../API/Rest/ModifyGuildEmbedParams.cs | 18 - .../API/Rest/ModifyGuildIntegrationParams.cs | 21 - .../API/Rest/ModifyGuildMemberParams.cs | 33 -- src/Discord.Net/API/Rest/ModifyGuildParams.cs | 52 -- .../API/Rest/ModifyGuildRoleParams.cs | 29 - .../API/Rest/ModifyPresenceParams.cs | 12 - src/Discord.Net/API/Rest/UploadFileParams.cs | 42 -- src/Discord.Net/Discord.Net.xproj | 6 +- .../Entities/Channels/IGroupChannel.cs | 13 - src/Discord.Net/Entities/Guilds/Ban.cs | 20 - src/Discord.Net/Entities/Guilds/Emoji.cs | 28 - src/Discord.Net/Entities/IEntity.cs | 15 - src/Discord.Net/Entities/ISnowflakeEntity.cs | 10 - .../Entities/Messages/ISystemMessage.cs | 8 - src/Discord.Net/Entities/UpdateSource.cs | 9 - src/Discord.Net/Entities/Users/IGroupUser.cs | 13 - src/Discord.Net/Logging/ILogManager.cs | 36 -- src/Discord.Net/Logging/ILogger.cs | 34 -- src/Discord.Net/Properties/AssemblyInfo.cs | 19 + .../Rest/Entities/Channels/DMChannel.cs | 139 ----- .../Rest/Entities/Channels/GroupChannel.cs | 164 ------ .../Rest/Entities/Channels/TextChannel.cs | 133 ----- .../Rest/Entities/Channels/VoiceChannel.cs | 57 -- src/Discord.Net/Rest/Entities/Entity.cs | 21 - src/Discord.Net/Rest/Entities/Guilds/Guild.cs | 307 ----------- .../Rest/Entities/Guilds/VoiceRegion.cs | 29 - .../Rest/Entities/Messages/Attachment.cs | 26 - .../Rest/Entities/Messages/Embed.cs | 27 - .../Rest/Entities/Messages/Message.cs | 100 ---- .../Rest/Entities/Messages/SystemMessage.cs | 22 - .../Rest/Entities/Messages/UserMessage.cs | 175 ------ src/Discord.Net/Rest/Entities/Roles/Role.cs | 67 --- .../Rest/Entities/SnowflakeEntity.cs | 15 - .../Rest/Entities/Users/Connection.cs | 30 - .../Rest/Entities/Users/GroupUser.cs | 49 -- .../Rest/Entities/Users/GuildUser.cs | 164 ------ .../Rest/Entities/Users/SelfUser.cs | 68 --- src/Discord.Net/Rest/Entities/Users/User.cs | 47 -- .../Entities/Channels/ISocketChannel.cs | 11 - .../Entities/Channels/ISocketGuildChannel.cs | 7 - .../Channels/ISocketMessageChannel.cs | 17 - .../Channels/ISocketPrivateChannel.cs | 9 - .../WebSocket/Extensions/ChannelExtensions.cs | 65 --- .../Extensions/DiscordClientExtensions.cs | 53 -- .../WebSocket/Extensions/GuildExtensions.cs | 51 -- src/Discord.Net/project.json | 48 +- 394 files changed, 3648 insertions(+), 3224 deletions(-) rename src/{Discord.Net => Discord.Net.Core}/API/CDN.cs (96%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Application.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Attachment.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Ban.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Channel.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Connection.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Embed.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/EmbedProvider.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/EmbedThumbnail.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Emoji.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Game.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Guild.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/GuildEmbed.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/GuildMember.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Integration.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/IntegrationAccount.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Invite.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/InviteChannel.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/InviteGuild.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/InviteMetadata.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Message.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Overwrite.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Presence.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/ReadState.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Relationship.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/RelationshipType.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/Role.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/User.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/UserGuild.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/VoiceRegion.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Common/VoiceState.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/DiscordRestApiClient.cs (75%) rename src/{Discord.Net => Discord.Net.Core}/API/Image.cs (93%) rename src/{Discord.Net => Discord.Net.Core}/API/Int53Attribute.cs (100%) create mode 100644 src/Discord.Net.Core/API/Rest/CreateChannelInviteParams.cs rename src/{Discord.Net => Discord.Net.Core}/API/Rest/CreateDMChannelParams.cs (56%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/CreateGuildBanParams.cs (64%) create mode 100644 src/Discord.Net.Core/API/Rest/CreateGuildChannelParams.cs rename src/{Discord.Net => Discord.Net.Core}/API/Rest/CreateGuildIntegrationParams.cs (57%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/CreateGuildParams.cs (52%) create mode 100644 src/Discord.Net.Core/API/Rest/CreateMessageParams.cs create mode 100644 src/Discord.Net.Core/API/Rest/DeleteMessagesParams.cs create mode 100644 src/Discord.Net.Core/API/Rest/GetChannelMessagesParams.cs rename src/{Discord.Net => Discord.Net.Core}/API/Rest/GetGatewayResponse.cs (100%) create mode 100644 src/Discord.Net.Core/API/Rest/GetGuildMembersParams.cs rename src/{Discord.Net => Discord.Net.Core}/API/Rest/GetGuildPruneCountResponse.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/GuildPruneParams.cs (65%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyChannelPermissionsParams.cs (51%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyCurrentUserNickParams.cs (61%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyCurrentUserParams.cs (51%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyGuildChannelParams.cs (55%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyGuildChannelsParams.cs (55%) create mode 100644 src/Discord.Net.Core/API/Rest/ModifyGuildEmbedParams.cs create mode 100644 src/Discord.Net.Core/API/Rest/ModifyGuildIntegrationParams.cs create mode 100644 src/Discord.Net.Core/API/Rest/ModifyGuildMemberParams.cs create mode 100644 src/Discord.Net.Core/API/Rest/ModifyGuildParams.cs create mode 100644 src/Discord.Net.Core/API/Rest/ModifyGuildRoleParams.cs rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyGuildRolesParams.cs (67%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyMessageParams.cs (67%) create mode 100644 src/Discord.Net.Core/API/Rest/ModifyPresenceParams.cs rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyTextChannelParams.cs (70%) rename src/{Discord.Net => Discord.Net.Core}/API/Rest/ModifyVoiceChannelParams.cs (57%) create mode 100644 src/Discord.Net.Core/API/Rest/UploadFileParams.cs rename src/{Discord.Net => Discord.Net.Core}/API/WebSocketMessage.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Audio/IAudioClient.cs (65%) rename src/{Discord.Net => Discord.Net.Core}/Audio/Opus/OpusApplication.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/ConnectionState.cs (100%) create mode 100644 src/Discord.Net.Core/Discord.Net.Core.xproj rename src/{Discord.Net => Discord.Net.Core}/DiscordConfig.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Channels/ChannelType.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Channels/IChannel.cs (57%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Channels/IDMChannel.cs (100%) create mode 100644 src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs rename src/{Discord.Net => Discord.Net.Core}/Entities/Channels/IGuildChannel.cs (89%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Channels/IMessageChannel.cs (82%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Channels/IPrivateChannel.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Channels/ITextChannel.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Channels/IVoiceChannel.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Guilds/DefaultMessageNotifications.cs (100%) create mode 100644 src/Discord.Net.Core/Entities/Guilds/Emoji.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/IBan.cs rename src/{Discord.Net => Discord.Net.Core}/Entities/Guilds/IGuild.cs (89%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Guilds/IGuildIntegration.cs (83%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Guilds/IUserGuild.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Guilds/IVoiceRegion.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Guilds/IntegrationAccount.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Guilds/MfaLevel.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Guilds/VerificationLevel.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/IApplication.cs (77%) rename src/{Discord.Net => Discord.Net.Core}/Entities/IDeletable.cs (100%) create mode 100644 src/Discord.Net.Core/Entities/IEntity.cs rename src/{Discord.Net => Discord.Net.Core}/Entities/IMentionable.cs (100%) create mode 100644 src/Discord.Net.Core/Entities/ISnowflakeEntity.cs rename src/{Discord.Net => Discord.Net.Core}/Entities/IUpdateable.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Invites/IInvite.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Invites/IInviteMetadata.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Messages/Direction.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Messages/EmbedProvider.cs (69%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Messages/EmbedThumbnail.cs (81%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Messages/IAttachment.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Messages/IEmbed.cs (69%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Messages/IMessage.cs (85%) create mode 100644 src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs rename src/{Discord.Net => Discord.Net.Core}/Entities/Messages/IUserMessage.cs (100%) rename src/{Discord.Net/Entities/Messages => Discord.Net.Core/Entities/Messages/Mentions}/ChannelMentionHandling.cs (100%) rename src/{Discord.Net/Entities/Messages => Discord.Net.Core/Entities/Messages/Mentions}/EveryoneMentionHandling.cs (100%) rename src/{Discord.Net/Entities/Messages => Discord.Net.Core/Entities/Messages/Mentions}/RoleMentionHandling.cs (100%) rename src/{Discord.Net/Entities/Messages => Discord.Net.Core/Entities/Messages/Mentions}/UserMentionHandling.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Messages/MessageType.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Permissions/ChannelPermission.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Permissions/ChannelPermissions.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Permissions/GuildPermission.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Permissions/GuildPermissions.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Permissions/Overwrite.cs (96%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Permissions/OverwritePermissions.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Permissions/PermValue.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Permissions/PermissionTarget.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Roles/Color.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Roles/IRole.cs (64%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/Game.cs (71%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/IConnection.cs (100%) create mode 100644 src/Discord.Net.Core/Entities/Users/IGroupUser.cs rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/IGuildUser.cs (53%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/IPresence.cs (89%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/ISelfUser.cs (91%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/IUser.cs (57%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/IVoiceState.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/StreamType.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Entities/Users/UserStatus.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Extensions/DiscordClientExtensions.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Extensions/GuildExtensions.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Extensions/GuildUserExtensions.cs (75%) create mode 100644 src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs rename src/{Discord.Net => Discord.Net.Core}/Format.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/IDiscordClient.cs (92%) rename src/{Discord.Net => Discord.Net.Core}/Logging/LogMessage.cs (100%) rename src/{Discord.Net => Discord.Net.Core/Logging}/LogSeverity.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/LoginState.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/DiscordContractResolver.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/ImageConverter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/NullableUInt64Converter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/OptionalConverter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/PermissionTargetConverter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/StringEntityConverter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/UInt64ArrayConverter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/UInt64Converter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/UInt64EntityConverter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Converters/UserStatusConverter.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/HttpException.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/Definitions/BucketDefinition.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/Definitions/BucketGroup.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/Definitions/BucketTarget.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/Definitions/ChannelBucket.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/Definitions/GlobalBucket.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/Definitions/GuildBucket.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/IQueuedRequest.cs (89%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/RequestQueue.cs (97%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/RequestQueueBucket.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/RestRequest.cs (97%) rename src/{Discord.Net => Discord.Net.Core}/Net/Queue/WebSocketRequest.cs (65%) rename src/{Discord.Net => Discord.Net.Core}/Net/RateLimitException.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Rest/DefaultRestClient.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Rest/IRestClient.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Rest/MultipartFile.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/Rest/RestClientProvider.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/RpcException.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/WebSocketException.cs (100%) rename src/{Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs => Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs} (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/WebSockets/IWebSocketClient.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/Net/WebSockets/WebSocketProvider.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/RequestOptions.cs (100%) rename src/{Discord.Net => Discord.Net.Core}/TokenType.cs (100%) create mode 100644 src/Discord.Net.Core/Utils/MentionUtils.cs rename src/{Discord.Net/Utilities => Discord.Net.Core/Utils}/Optional.cs (100%) create mode 100644 src/Discord.Net.Core/project.json create mode 100644 src/Discord.Net.Rest/Discord.Net.Rest.xproj rename src/{Discord.Net/Rest => Discord.Net.Rest}/DiscordRestClient.cs (65%) rename src/{Discord.Net/Rest => Discord.Net.Rest}/DiscordRestConfig.cs (100%) create mode 100644 src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs create mode 100644 src/Discord.Net.Rest/Entities/Guilds/RestBan.cs create mode 100644 src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs rename src/{Discord.Net/Entities/Guilds/GuildEmbed.cs => Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs} (67%) create mode 100644 src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs rename src/{Discord.Net/Rest/Entities/Guilds/UserGuild.cs => Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs} (69%) create mode 100644 src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs create mode 100644 src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs rename src/{Discord.Net/Rest/Entities/Invites/Invite.cs => Discord.Net.Rest/Entities/Invites/RestInvite.cs} (50%) rename src/{Discord.Net/Rest/Entities/Invites/InviteMetadata.cs => Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs} (50%) create mode 100644 src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/RestMessage.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs rename src/{Discord.Net/Rest/Entities/Application.cs => Discord.Net.Rest/Entities/RestApplication.cs} (61%) create mode 100644 src/Discord.Net.Rest/Entities/RestEntity.cs create mode 100644 src/Discord.Net.Rest/Entities/Roles/RestRole.cs create mode 100644 src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs create mode 100644 src/Discord.Net.Rest/Entities/Users/RestConnection.cs create mode 100644 src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs create mode 100644 src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs create mode 100644 src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs create mode 100644 src/Discord.Net.Rest/Entities/Users/RestUser.cs create mode 100644 src/Discord.Net.Rest/Entities/Users/UserHelper.cs create mode 100644 src/Discord.Net.Rest/Properties/AssemblyInfo.cs create mode 100644 src/Discord.Net.Rest/project.json rename src/{Discord.Net/API/DiscordRpcAPIClient.cs => Discord.Net.Rpc/API/DiscordRpcApiClient.cs} (99%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/Application.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/AuthenticateParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/AuthenticateResponse.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/AuthorizeParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/AuthorizeResponse.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/ChannelSubscriptionParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/ErrorEvent.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/GetChannelParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/GetChannelsParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/GetChannelsResponse.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/GetGuildParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/GetGuildsParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/GetGuildsResponse.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/GuildStatusEvent.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/GuildSubscriptionParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/MessageEvent.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/ReadyEvent.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/RpcChannel.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/RpcConfig.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/RpcGuild.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/RpcMessage.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/RpcUserGuild.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/SelectVoiceChannelParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/SetLocalVolumeParams.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/SetLocalVolumeResponse.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/SpeakingEvent.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/SubscriptionResponse.cs (100%) rename src/{Discord.Net => Discord.Net.Rpc}/API/Rpc/VoiceStateEvent.cs (100%) create mode 100644 src/Discord.Net.Rpc/Discord.Net.Rpc.xproj rename src/{Discord.Net/Rpc => Discord.Net.Rpc}/DiscordRpcClient.Events.cs (100%) rename src/{Discord.Net/Rpc => Discord.Net.Rpc}/DiscordRpcClient.cs (100%) rename src/{Discord.Net/Rpc => Discord.Net.Rpc}/DiscordRpcConfig.cs (100%) rename src/{Discord.Net/Rpc => Discord.Net.Rpc}/Entities/IRemoteUserGuild.cs (100%) rename src/{Discord.Net/Rpc => Discord.Net.Rpc}/Entities/RemoteUserGuild.cs (78%) rename src/{Discord.Net/Rpc => Discord.Net.Rpc}/Entities/RpcMessage.cs (100%) create mode 100644 src/Discord.Net.Rpc/Properties/AssemblyInfo.cs rename src/{Discord.Net/Rpc => Discord.Net.Rpc}/RpcChannelEvent.cs (100%) rename src/{Discord.Net/Rpc => Discord.Net.Rpc}/RpcGuildEvent.cs (100%) create mode 100644 src/Discord.Net.Rpc/project.json rename src/{Discord.Net/Utilities => Discord.Net.Utils}/AsyncEvent.cs (100%) rename src/{Discord.Net/Utilities => Discord.Net.Utils}/ConcurrentHashSet.cs (100%) rename src/{Discord.Net/Utilities => Discord.Net.Utils}/DateTimeUtils.cs (100%) create mode 100644 src/Discord.Net.Utils/Discord.Net.Utils.projitems create mode 100644 src/Discord.Net.Utils/Discord.Net.Utils.shproj rename src/{Discord.Net => Discord.Net.Utils}/Extensions/CollectionExtensions.cs (100%) create mode 100644 src/Discord.Net.Utils/Extensions/Permissions.cs rename src/{Discord.Net => Discord.Net.Utils}/Extensions/TaskCompletionSourceExtensions.cs (58%) rename src/{Discord.Net => Discord.Net.Utils}/Logging/LogManager.cs (55%) rename src/{Discord.Net => Discord.Net.Utils}/Logging/Logger.cs (98%) rename src/{Discord.Net/Utilities/MentionUtils.cs => Discord.Net.Utils/MentionsHelper.cs} (56%) create mode 100644 src/Discord.Net.Utils/Paging/Page.cs create mode 100644 src/Discord.Net.Utils/Paging/PageInfo.cs create mode 100644 src/Discord.Net.Utils/Paging/PagedEnumerator.cs rename src/{Discord.Net/Entities/Permissions => Discord.Net.Utils}/Permissions.cs (91%) rename src/{Discord.Net/Utilities => Discord.Net.Utils}/Preconditions.cs (99%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/DiscordSocketApiClient.cs (99%) rename src/{Discord.Net/API/DiscordVoiceAPIClient.cs => Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs} (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/ExtendedGuild.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GatewayOpCode.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildBanEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildEmojiUpdateEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildMemberAddEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildMemberRemoveEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildMemberUpdateEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildMembersChunkEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildRoleCreateEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildRoleDeleteEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildRoleUpdateEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/GuildSyncEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/HelloEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/IdentifyParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/MessageDeleteBulkEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/ReadyEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/RecipientEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/RequestMembersParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/ResumeParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/ResumedEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/StatusUpdateParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/TypingStartEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/UpdateStatusParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/VoiceServerUpdateEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Gateway/VoiceStateUpdateParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Voice/IdentifyParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Voice/ReadyEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Voice/SelectProtocolParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Voice/SessionDescriptionEvent.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Voice/SpeakingParams.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Voice/UdpProtocolInfo.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/API/Voice/VoiceOpCode.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/AudioClient.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/AudioMode.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Opus/OpusConverter.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Opus/OpusCtl.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Opus/OpusDecoder.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Opus/OpusEncoder.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Opus/OpusError.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Sodium/SecretBox.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Streams/OpusDecodeStream.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Streams/OpusEncodeStream.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Streams/RTPReadStream.cs (100%) rename src/{Discord.Net => Discord.Net.WebSocket}/Audio/Streams/RTPWriteStream.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/DataStore.cs (100%) create mode 100644 src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/DiscordSocketClient.Events.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/DiscordSocketClient.cs (99%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/DiscordSocketConfig.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Channels/SocketDMChannel.cs (93%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Channels/SocketGroupChannel.cs (93%) rename src/{Discord.Net/Rest/Entities/Channels/GuildChannel.cs => Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs} (96%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Channels/SocketTextChannel.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Channels/SocketVoiceChannel.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Guilds/SocketGuild.cs (98%) rename src/{Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs => Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs} (90%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Messages/ISocketMessage.cs (82%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Messages/SocketSystemMessage.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Messages/SocketUserMessage.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Users/ISocketUser.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Users/Presence.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Users/SocketDMUser.cs (95%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Users/SocketGlobalUser.cs (91%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Users/SocketGroupUser.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Users/SocketGuildUser.cs (96%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Users/SocketSelfUser.cs (100%) rename src/{Discord.Net/WebSocket => Discord.Net.WebSocket}/Entities/Users/VoiceState.cs (100%) rename src/{Discord.Net/WebSocket/Entities/Channels => Discord.Net.WebSocket/Entities/Utilities}/MessageCache.cs (100%) rename src/{Discord.Net/WebSocket/Entities/Channels => Discord.Net.WebSocket/Entities/Utilities}/MessageManager.cs (100%) create mode 100644 src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs create mode 100644 src/Discord.Net.WebSocket/project.json delete mode 100644 src/Discord.Net/API/Rest/CreateChannelInviteParams.cs delete mode 100644 src/Discord.Net/API/Rest/CreateGuildChannelParams.cs delete mode 100644 src/Discord.Net/API/Rest/CreateMessageParams.cs delete mode 100644 src/Discord.Net/API/Rest/DeleteMessagesParams.cs delete mode 100644 src/Discord.Net/API/Rest/GetChannelMessagesParams.cs delete mode 100644 src/Discord.Net/API/Rest/GetGuildMembersParams.cs delete mode 100644 src/Discord.Net/API/Rest/ModifyGuildEmbedParams.cs delete mode 100644 src/Discord.Net/API/Rest/ModifyGuildIntegrationParams.cs delete mode 100644 src/Discord.Net/API/Rest/ModifyGuildMemberParams.cs delete mode 100644 src/Discord.Net/API/Rest/ModifyGuildParams.cs delete mode 100644 src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs delete mode 100644 src/Discord.Net/API/Rest/ModifyPresenceParams.cs delete mode 100644 src/Discord.Net/API/Rest/UploadFileParams.cs delete mode 100644 src/Discord.Net/Entities/Channels/IGroupChannel.cs delete mode 100644 src/Discord.Net/Entities/Guilds/Ban.cs delete mode 100644 src/Discord.Net/Entities/Guilds/Emoji.cs delete mode 100644 src/Discord.Net/Entities/IEntity.cs delete mode 100644 src/Discord.Net/Entities/ISnowflakeEntity.cs delete mode 100644 src/Discord.Net/Entities/Messages/ISystemMessage.cs delete mode 100644 src/Discord.Net/Entities/UpdateSource.cs delete mode 100644 src/Discord.Net/Entities/Users/IGroupUser.cs delete mode 100644 src/Discord.Net/Logging/ILogManager.cs delete mode 100644 src/Discord.Net/Logging/ILogger.cs create mode 100644 src/Discord.Net/Properties/AssemblyInfo.cs delete mode 100644 src/Discord.Net/Rest/Entities/Channels/DMChannel.cs delete mode 100644 src/Discord.Net/Rest/Entities/Channels/GroupChannel.cs delete mode 100644 src/Discord.Net/Rest/Entities/Channels/TextChannel.cs delete mode 100644 src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs delete mode 100644 src/Discord.Net/Rest/Entities/Entity.cs delete mode 100644 src/Discord.Net/Rest/Entities/Guilds/Guild.cs delete mode 100644 src/Discord.Net/Rest/Entities/Guilds/VoiceRegion.cs delete mode 100644 src/Discord.Net/Rest/Entities/Messages/Attachment.cs delete mode 100644 src/Discord.Net/Rest/Entities/Messages/Embed.cs delete mode 100644 src/Discord.Net/Rest/Entities/Messages/Message.cs delete mode 100644 src/Discord.Net/Rest/Entities/Messages/SystemMessage.cs delete mode 100644 src/Discord.Net/Rest/Entities/Messages/UserMessage.cs delete mode 100644 src/Discord.Net/Rest/Entities/Roles/Role.cs delete mode 100644 src/Discord.Net/Rest/Entities/SnowflakeEntity.cs delete mode 100644 src/Discord.Net/Rest/Entities/Users/Connection.cs delete mode 100644 src/Discord.Net/Rest/Entities/Users/GroupUser.cs delete mode 100644 src/Discord.Net/Rest/Entities/Users/GuildUser.cs delete mode 100644 src/Discord.Net/Rest/Entities/Users/SelfUser.cs delete mode 100644 src/Discord.Net/Rest/Entities/Users/User.cs delete mode 100644 src/Discord.Net/WebSocket/Entities/Channels/ISocketChannel.cs delete mode 100644 src/Discord.Net/WebSocket/Entities/Channels/ISocketGuildChannel.cs delete mode 100644 src/Discord.Net/WebSocket/Entities/Channels/ISocketMessageChannel.cs delete mode 100644 src/Discord.Net/WebSocket/Entities/Channels/ISocketPrivateChannel.cs delete mode 100644 src/Discord.Net/WebSocket/Extensions/ChannelExtensions.cs delete mode 100644 src/Discord.Net/WebSocket/Extensions/DiscordClientExtensions.cs delete mode 100644 src/Discord.Net/WebSocket/Extensions/GuildExtensions.cs diff --git a/Discord.Net.sln b/Discord.Net.sln index 11960606b..4ce5a23fc 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -1,28 +1,50 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + README.md = README.md + EndProjectSection EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Utils", "src\Discord.Net.Utils\Discord.Net.Utils.shproj", "{2B75119C-9893-4AAA-8D38-6176EEB09060}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Discord.Net.Utils\Discord.Net.Utils.projitems*{2b75119c-9893-4aaa-8d38-6176eeb09060}*SharedItemsImports = 13 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Release|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU - {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {BFC6DC28-0351-4573-926A-D4124244C04F} = {288C363D-A636-4EAE-9AC1-4698B641B26E} + EndGlobalSection EndGlobal diff --git a/src/Discord.Net/API/CDN.cs b/src/Discord.Net.Core/API/CDN.cs similarity index 96% rename from src/Discord.Net/API/CDN.cs rename to src/Discord.Net.Core/API/CDN.cs index 3973344db..d7a3f1d39 100644 --- a/src/Discord.Net/API/CDN.cs +++ b/src/Discord.Net.Core/API/CDN.cs @@ -1,6 +1,6 @@ namespace Discord.API { - internal static class CDN + public static class CDN { public static string GetApplicationIconUrl(ulong appId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; diff --git a/src/Discord.Net/API/Common/Application.cs b/src/Discord.Net.Core/API/Common/Application.cs similarity index 100% rename from src/Discord.Net/API/Common/Application.cs rename to src/Discord.Net.Core/API/Common/Application.cs diff --git a/src/Discord.Net/API/Common/Attachment.cs b/src/Discord.Net.Core/API/Common/Attachment.cs similarity index 100% rename from src/Discord.Net/API/Common/Attachment.cs rename to src/Discord.Net.Core/API/Common/Attachment.cs diff --git a/src/Discord.Net/API/Common/Ban.cs b/src/Discord.Net.Core/API/Common/Ban.cs similarity index 100% rename from src/Discord.Net/API/Common/Ban.cs rename to src/Discord.Net.Core/API/Common/Ban.cs diff --git a/src/Discord.Net/API/Common/Channel.cs b/src/Discord.Net.Core/API/Common/Channel.cs similarity index 100% rename from src/Discord.Net/API/Common/Channel.cs rename to src/Discord.Net.Core/API/Common/Channel.cs diff --git a/src/Discord.Net/API/Common/Connection.cs b/src/Discord.Net.Core/API/Common/Connection.cs similarity index 100% rename from src/Discord.Net/API/Common/Connection.cs rename to src/Discord.Net.Core/API/Common/Connection.cs diff --git a/src/Discord.Net/API/Common/Embed.cs b/src/Discord.Net.Core/API/Common/Embed.cs similarity index 100% rename from src/Discord.Net/API/Common/Embed.cs rename to src/Discord.Net.Core/API/Common/Embed.cs diff --git a/src/Discord.Net/API/Common/EmbedProvider.cs b/src/Discord.Net.Core/API/Common/EmbedProvider.cs similarity index 100% rename from src/Discord.Net/API/Common/EmbedProvider.cs rename to src/Discord.Net.Core/API/Common/EmbedProvider.cs diff --git a/src/Discord.Net/API/Common/EmbedThumbnail.cs b/src/Discord.Net.Core/API/Common/EmbedThumbnail.cs similarity index 100% rename from src/Discord.Net/API/Common/EmbedThumbnail.cs rename to src/Discord.Net.Core/API/Common/EmbedThumbnail.cs diff --git a/src/Discord.Net/API/Common/Emoji.cs b/src/Discord.Net.Core/API/Common/Emoji.cs similarity index 100% rename from src/Discord.Net/API/Common/Emoji.cs rename to src/Discord.Net.Core/API/Common/Emoji.cs diff --git a/src/Discord.Net/API/Common/Game.cs b/src/Discord.Net.Core/API/Common/Game.cs similarity index 100% rename from src/Discord.Net/API/Common/Game.cs rename to src/Discord.Net.Core/API/Common/Game.cs diff --git a/src/Discord.Net/API/Common/Guild.cs b/src/Discord.Net.Core/API/Common/Guild.cs similarity index 100% rename from src/Discord.Net/API/Common/Guild.cs rename to src/Discord.Net.Core/API/Common/Guild.cs diff --git a/src/Discord.Net/API/Common/GuildEmbed.cs b/src/Discord.Net.Core/API/Common/GuildEmbed.cs similarity index 100% rename from src/Discord.Net/API/Common/GuildEmbed.cs rename to src/Discord.Net.Core/API/Common/GuildEmbed.cs diff --git a/src/Discord.Net/API/Common/GuildMember.cs b/src/Discord.Net.Core/API/Common/GuildMember.cs similarity index 100% rename from src/Discord.Net/API/Common/GuildMember.cs rename to src/Discord.Net.Core/API/Common/GuildMember.cs diff --git a/src/Discord.Net/API/Common/Integration.cs b/src/Discord.Net.Core/API/Common/Integration.cs similarity index 100% rename from src/Discord.Net/API/Common/Integration.cs rename to src/Discord.Net.Core/API/Common/Integration.cs diff --git a/src/Discord.Net/API/Common/IntegrationAccount.cs b/src/Discord.Net.Core/API/Common/IntegrationAccount.cs similarity index 100% rename from src/Discord.Net/API/Common/IntegrationAccount.cs rename to src/Discord.Net.Core/API/Common/IntegrationAccount.cs diff --git a/src/Discord.Net/API/Common/Invite.cs b/src/Discord.Net.Core/API/Common/Invite.cs similarity index 100% rename from src/Discord.Net/API/Common/Invite.cs rename to src/Discord.Net.Core/API/Common/Invite.cs diff --git a/src/Discord.Net/API/Common/InviteChannel.cs b/src/Discord.Net.Core/API/Common/InviteChannel.cs similarity index 100% rename from src/Discord.Net/API/Common/InviteChannel.cs rename to src/Discord.Net.Core/API/Common/InviteChannel.cs diff --git a/src/Discord.Net/API/Common/InviteGuild.cs b/src/Discord.Net.Core/API/Common/InviteGuild.cs similarity index 100% rename from src/Discord.Net/API/Common/InviteGuild.cs rename to src/Discord.Net.Core/API/Common/InviteGuild.cs diff --git a/src/Discord.Net/API/Common/InviteMetadata.cs b/src/Discord.Net.Core/API/Common/InviteMetadata.cs similarity index 100% rename from src/Discord.Net/API/Common/InviteMetadata.cs rename to src/Discord.Net.Core/API/Common/InviteMetadata.cs diff --git a/src/Discord.Net/API/Common/Message.cs b/src/Discord.Net.Core/API/Common/Message.cs similarity index 100% rename from src/Discord.Net/API/Common/Message.cs rename to src/Discord.Net.Core/API/Common/Message.cs diff --git a/src/Discord.Net/API/Common/Overwrite.cs b/src/Discord.Net.Core/API/Common/Overwrite.cs similarity index 100% rename from src/Discord.Net/API/Common/Overwrite.cs rename to src/Discord.Net.Core/API/Common/Overwrite.cs diff --git a/src/Discord.Net/API/Common/Presence.cs b/src/Discord.Net.Core/API/Common/Presence.cs similarity index 100% rename from src/Discord.Net/API/Common/Presence.cs rename to src/Discord.Net.Core/API/Common/Presence.cs diff --git a/src/Discord.Net/API/Common/ReadState.cs b/src/Discord.Net.Core/API/Common/ReadState.cs similarity index 100% rename from src/Discord.Net/API/Common/ReadState.cs rename to src/Discord.Net.Core/API/Common/ReadState.cs diff --git a/src/Discord.Net/API/Common/Relationship.cs b/src/Discord.Net.Core/API/Common/Relationship.cs similarity index 100% rename from src/Discord.Net/API/Common/Relationship.cs rename to src/Discord.Net.Core/API/Common/Relationship.cs diff --git a/src/Discord.Net/API/Common/RelationshipType.cs b/src/Discord.Net.Core/API/Common/RelationshipType.cs similarity index 100% rename from src/Discord.Net/API/Common/RelationshipType.cs rename to src/Discord.Net.Core/API/Common/RelationshipType.cs diff --git a/src/Discord.Net/API/Common/Role.cs b/src/Discord.Net.Core/API/Common/Role.cs similarity index 100% rename from src/Discord.Net/API/Common/Role.cs rename to src/Discord.Net.Core/API/Common/Role.cs diff --git a/src/Discord.Net/API/Common/User.cs b/src/Discord.Net.Core/API/Common/User.cs similarity index 100% rename from src/Discord.Net/API/Common/User.cs rename to src/Discord.Net.Core/API/Common/User.cs diff --git a/src/Discord.Net/API/Common/UserGuild.cs b/src/Discord.Net.Core/API/Common/UserGuild.cs similarity index 100% rename from src/Discord.Net/API/Common/UserGuild.cs rename to src/Discord.Net.Core/API/Common/UserGuild.cs diff --git a/src/Discord.Net/API/Common/VoiceRegion.cs b/src/Discord.Net.Core/API/Common/VoiceRegion.cs similarity index 100% rename from src/Discord.Net/API/Common/VoiceRegion.cs rename to src/Discord.Net.Core/API/Common/VoiceRegion.cs diff --git a/src/Discord.Net/API/Common/VoiceState.cs b/src/Discord.Net.Core/API/Common/VoiceState.cs similarity index 100% rename from src/Discord.Net/API/Common/VoiceState.cs rename to src/Discord.Net.Core/API/Common/VoiceState.cs diff --git a/src/Discord.Net/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs similarity index 75% rename from src/Discord.Net/API/DiscordRestApiClient.cs rename to src/Discord.Net.Core/API/DiscordRestApiClient.cs index 0d0b22c25..8946de96b 100644 --- a/src/Discord.Net/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -4,7 +4,6 @@ using Discord.Net; using Discord.Net.Converters; using Discord.Net.Queue; using Discord.Net.Rest; -using Discord.Rest; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -28,6 +27,7 @@ namespace Discord.API protected readonly JsonSerializer _serializer; protected readonly SemaphoreSlim _stateLock; private readonly RestClientProvider _restClientProvider; + private readonly string _userAgent; protected string _authToken; protected bool _isDisposed; @@ -36,11 +36,13 @@ namespace Discord.API public LoginState LoginState { get; private set; } public TokenType AuthTokenType { get; private set; } - internal RequestQueue RequestQueue { get; private set; } + public User CurrentUser { get; private set; } + public RequestQueue RequestQueue { get; private set; } - public DiscordRestApiClient(RestClientProvider restClientProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) + public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, JsonSerializer serializer = null, RequestQueue requestQueue = null) { _restClientProvider = restClientProvider; + _userAgent = userAgent; _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; RequestQueue = requestQueue; @@ -52,7 +54,7 @@ namespace Discord.API { _restClient = _restClientProvider(baseUrl); _restClient.SetHeader("accept", "*/*"); - _restClient.SetHeader("user-agent", DiscordRestConfig.UserAgent); + _restClient.SetHeader("user-agent", _userAgent); _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); } internal static string GetPrefixedToken(TokenType tokenType, string token) @@ -111,6 +113,8 @@ namespace Discord.API _authToken = token; _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); + CurrentUser = await GetMyUserAsync(); + LoginState = LoginState.LoggedIn; } catch (Exception) @@ -144,6 +148,7 @@ namespace Discord.API await RequestQueue.SetCancelTokenAsync(CancellationToken.None).ConfigureAwait(false); _restClient.SetCancelToken(CancellationToken.None); + CurrentUser = null; LoginState = LoginState.LoggedOut; } @@ -268,8 +273,8 @@ namespace Discord.API { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.GreaterThan(args._bitrate, 0, nameof(args.Bitrate)); - Preconditions.NotNullOrWhitespace(args._name, nameof(args.Name)); + Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); return await SendAsync("POST", $"guilds/{guildId}/channels", args, options: options).ConfigureAwait(false); } @@ -283,8 +288,8 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.AtLeast(args._position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); + Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); return await SendAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); } @@ -292,8 +297,8 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.AtLeast(args._position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); + Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); return await SendAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); } @@ -301,10 +306,10 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.GreaterThan(args._bitrate, 0, nameof(args.Bitrate)); - Preconditions.AtLeast(args._userLimit, 0, nameof(args.Bitrate)); - Preconditions.AtLeast(args._position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); + Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); + Preconditions.AtLeast(args.UserLimit, 0, nameof(args.Bitrate)); + Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); return await SendAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); } @@ -326,6 +331,132 @@ namespace Discord.API break; } } + + //Channel Messages + public async Task GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + try + { + return await SendAsync("GET", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } + } + public async Task> GetChannelMessagesAsync(ulong channelId, GetChannelMessagesParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); + Preconditions.AtMost(args.Limit, DiscordConfig.MaxMessagesPerBatch, nameof(args.Limit)); + + int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxMessagesPerBatch); + ulong? relativeId = args.RelativeMessageId.IsSpecified ? args.RelativeMessageId.Value : (ulong?)null; + string relativeDir; + + switch (args.RelativeDirection.GetValueOrDefault(Direction.Before)) + { + case Direction.Before: + default: + relativeDir = "before"; + break; + case Direction.After: + relativeDir = "after"; + break; + case Direction.Around: + relativeDir = "around"; + break; + } + + string endpoint; + if (relativeId != null) + endpoint = $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; + else + endpoint = $"channels/{channelId}/messages?limit={limit}"; + return await SendAsync>("GET", endpoint, options: options).ConfigureAwait(false); + } + public async Task CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + 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)); + + return await SendAsync("POST", $"channels/{channelId}/messages", args, GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); + } + public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + if (args.Content.GetValueOrDefault(null) == null) + args.Content = ""; + else if (args.Content.IsSpecified) + { + 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)); + } + + return await SendMultipartAsync("POST", $"channels/{channelId}/messages", args.ToDictionary(), GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); + } + public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + await SendAsync("DELETE", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); + } + public async Task DeleteMessagesAsync(ulong channelId, DeleteMessagesParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + + Preconditions.NotNull(args.MessageIds, nameof(args.MessageIds)); + Preconditions.AtMost(args.MessageIds.Length, 100, nameof(args.MessageIds.Length)); + + switch (args.MessageIds.Length) + { + case 0: + return; + case 1: + await DeleteMessageAsync(channelId, args.MessageIds[0]).ConfigureAwait(false); + break; + default: + await SendAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, options: options).ConfigureAwait(false); + break; + } + } + 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)); + Preconditions.NotNull(args, nameof(args)); + if (args.Content.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)); + } + + return await SendAsync("PATCH", $"channels/{channelId}/messages/{messageId}", args, options: options).ConfigureAwait(false); + } + public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + await SendAsync("POST", $"channels/{channelId}/messages/{messageId}/ack", options: options).ConfigureAwait(false); + } + public async Task TriggerTypingIndicatorAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + await SendAsync("POST", $"channels/{channelId}/typing", options: options).ConfigureAwait(false); + } //Channel Permissions public async Task ModifyChannelPermissionsAsync(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args, RequestOptions options = null) @@ -399,7 +530,7 @@ namespace Discord.API { Preconditions.NotNull(args, nameof(args)); Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); - Preconditions.NotNullOrWhitespace(args.Region, nameof(args.Region)); + Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); return await SendAsync("POST", "guilds", args, options: options).ConfigureAwait(false); } @@ -419,11 +550,11 @@ namespace Discord.API { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.NotEqual(args._afkChannelId, 0, nameof(args.AFKChannelId)); - Preconditions.AtLeast(args._afkTimeout, 0, nameof(args.AFKTimeout)); - Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); - Preconditions.GreaterThan(args._ownerId, 0, nameof(args.OwnerId)); - Preconditions.NotNull(args._region, nameof(args.Region)); + Preconditions.NotEqual(args.AfkChannelId, 0, nameof(args.AfkChannelId)); + Preconditions.AtLeast(args.AfkTimeout, 0, nameof(args.AfkTimeout)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.GreaterThan(args.OwnerId, 0, nameof(args.OwnerId)); + Preconditions.NotNull(args.RegionId, nameof(args.RegionId)); return await SendAsync("PATCH", $"guilds/{guildId}", args, options: options).ConfigureAwait(false); } @@ -456,7 +587,7 @@ namespace Discord.API 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)); + Preconditions.AtLeast(args.DeleteMessageDays, 0, nameof(args.DeleteMessageDays)); await SendAsync("PUT", $"guilds/{guildId}/bans/{userId}", args, options: options).ConfigureAwait(false); } @@ -514,8 +645,8 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.AtLeast(args._expireBehavior, 0, nameof(args.ExpireBehavior)); - Preconditions.AtLeast(args._expireGracePeriod, 0, nameof(args.ExpireGracePeriod)); + Preconditions.AtLeast(args.ExpireBehavior, 0, nameof(args.ExpireBehavior)); + Preconditions.AtLeast(args.ExpireGracePeriod, 0, nameof(args.ExpireGracePeriod)); return await SendAsync("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args, options: options).ConfigureAwait(false); } @@ -552,18 +683,18 @@ namespace Discord.API return await SendAsync>("GET", $"guilds/{guildId}/invites", 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)); - return await SendAsync("GET", $"channels/{channelId}/invites", options: options).ConfigureAwait(false); + return await SendAsync>("GET", $"channels/{channelId}/invites", options: options).ConfigureAwait(false); } public async Task CreateChannelInviteAsync(ulong channelId, CreateChannelInviteParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.AtLeast(args._maxAge, 0, nameof(args.MaxAge)); - Preconditions.AtLeast(args._maxUses, 0, nameof(args.MaxUses)); + Preconditions.AtLeast(args.MaxAge, 0, nameof(args.MaxAge)); + Preconditions.AtLeast(args.MaxUses, 0, nameof(args.MaxUses)); return await SendAsync("POST", $"channels/{channelId}/invites", args, options: options).ConfigureAwait(false); } @@ -596,42 +727,15 @@ namespace Discord.API { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.GreaterThan(args._limit, 0, nameof(args.Limit)); - Preconditions.GreaterThan(args._afterUserId, 0, nameof(args.AfterUserId)); - - int limit = args._limit.GetValueOrDefault(int.MaxValue); - ulong afterUserId = args._afterUserId.GetValueOrDefault(0); + Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); + Preconditions.AtMost(args.Limit, DiscordConfig.MaxUsersPerBatch, nameof(args.Limit)); + Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); - List result; - if (args._limit.IsSpecified) - result = new List((limit + DiscordConfig.MaxUsersPerBatch - 1) / DiscordConfig.MaxUsersPerBatch); - else - result = new List(); - - while (true) - { - int runLimit = (limit >= DiscordConfig.MaxUsersPerBatch) ? DiscordConfig.MaxUsersPerBatch : limit; - string endpoint = $"guilds/{guildId}/members?limit={runLimit}&after={afterUserId}"; - var models = await SendAsync("GET", endpoint, options: options).ConfigureAwait(false); - - //Was this an empty batch? - if (models.Length == 0) break; - - result.Add(models); - - limit -= DiscordConfig.MaxUsersPerBatch; - afterUserId = models[models.Length - 1].User.Id; - - //Was this an incomplete (the last) batch? - if (models.Length != DiscordConfig.MaxUsersPerBatch) break; - } - - if (result.Count > 1) - return result.SelectMany(x => x).ToImmutableArray(); - else if (result.Count == 1) - return result[0]; - else - return ImmutableArray.Create(); + int limit = args.Limit.GetValueOrDefault(int.MaxValue); + ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); + + string endpoint = $"guilds/{guildId}/members?limit={limit}&after={afterUserId}"; + return await SendAsync>("GET", endpoint, options: options).ConfigureAwait(false); } public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null) { @@ -646,7 +750,18 @@ namespace Discord.API Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotNull(args, nameof(args)); - await SendAsync("PATCH", $"guilds/{guildId}/members/{userId}", args, GuildBucket.ModifyMember, guildId, options: options).ConfigureAwait(false); + bool isCurrentUser = userId == CurrentUser.Id; + + if (isCurrentUser && args.Nickname.IsSpecified) + { + 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) + { + await SendAsync("PATCH", $"guilds/{guildId}/members/{userId}", args, GuildBucket.ModifyMember, guildId, options: options).ConfigureAwait(false); + } } //Guild Roles @@ -674,9 +789,9 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(roleId, 0, nameof(roleId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.AtLeast(args._color, 0, nameof(args.Color)); - Preconditions.NotNullOrEmpty(args._name, nameof(args.Name)); - Preconditions.AtLeast(args._position, 0, nameof(args.Position)); + Preconditions.AtLeast(args.Color, 0, nameof(args.Color)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); return await SendAsync("PATCH", $"guilds/{guildId}/roles/{roleId}", args, options: options).ConfigureAwait(false); } @@ -697,266 +812,6 @@ namespace Discord.API } } - //Messages - public async Task GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - - try - { - return await SendAsync("GET", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } - } - public async Task> GetChannelMessagesAsync(ulong channelId, GetChannelMessagesParams args, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotNull(args, nameof(args)); - Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); - - int limit = args.Limit; - ulong? relativeId = args._relativeMessageId.IsSpecified ? args._relativeMessageId.Value : (ulong?)null; - string relativeDir; - - switch (args.RelativeDirection) - { - case Direction.Before: - default: - relativeDir = "before"; - break; - case Direction.After: - relativeDir = "after"; - break; - case Direction.Around: - relativeDir = "around"; - break; - } - - int runs = (limit + DiscordConfig.MaxMessagesPerBatch - 1) / DiscordConfig.MaxMessagesPerBatch; - int lastRunCount = limit - (runs - 1) * DiscordConfig.MaxMessagesPerBatch; - var result = new API.Message[runs][]; - - int i = 0; - for (; i < runs; i++) - { - int runCount = i == (runs - 1) ? lastRunCount : DiscordConfig.MaxMessagesPerBatch; - string endpoint; - if (relativeId != null) - endpoint = $"channels/{channelId}/messages?limit={runCount}&{relativeDir}={relativeId}"; - else - endpoint = $"channels/{channelId}/messages?limit={runCount}"; - var models = await SendAsync("GET", endpoint, options: options).ConfigureAwait(false); - - //Was this an empty batch? - if (models.Length == 0) break; - - //We can't assume these messages to be sorted by id (fails in rare cases), lets search for the highest/lowest id ourselves - switch (args.RelativeDirection) - { - case Direction.Before: - case Direction.Around: - default: - result[i] = models; - relativeId = ulong.MaxValue; - //Lowest id *should* be the last one - for (int j = models.Length - 1; j >= 0; j--) - { - if (models[j].Id < relativeId.Value) - relativeId = models[j].Id; - } - break; - case Direction.After: - result[runs - i - 1] = models; - relativeId = ulong.MinValue; - //Highest id *should* be the first one - for (int j = 0; j < models.Length; j++) - { - if (models[j].Id > relativeId.Value) - relativeId = models[j].Id; - } - break; - } - - //Was this an incomplete (the last) batch? - if (models.Length != DiscordConfig.MaxMessagesPerBatch) { i++; break; } - } - - if (i > 1) - { - switch (args.RelativeDirection) - { - case Direction.Before: - case Direction.Around: - default: - return result.Take(i).SelectMany(x => x).ToImmutableArray(); - case Direction.After: - return result.Skip(runs - i).Take(i).SelectMany(x => x).ToImmutableArray(); - } - } - else if (i == 1) - { - switch (args.RelativeDirection) - { - case Direction.Before: - case Direction.Around: - default: - return result[0]; - case Direction.After: - return result[runs - 1]; - } - } - else - return ImmutableArray.Create(); - } - public Task CreateMessageAsync(ulong guildId, ulong channelId, CreateMessageParams args, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - - return CreateMessageInternalAsync(guildId, channelId, args); - } - public Task CreateDMMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) - { - return CreateMessageInternalAsync(0, channelId, args); - } - private async Task CreateMessageInternalAsync(ulong guildId, ulong channelId, CreateMessageParams args, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotNull(args, nameof(args)); - 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)); - - if (guildId != 0) - return await SendAsync("POST", $"channels/{channelId}/messages", args, GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false); - else - return await SendAsync("POST", $"channels/{channelId}/messages", args, GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); - } - public Task UploadFileAsync(ulong guildId, ulong channelId, UploadFileParams args, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - - return UploadFileInternalAsync(guildId, channelId, args); - } - public Task UploadDMFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) - { - return UploadFileInternalAsync(0, channelId, args); - } - private async Task UploadFileInternalAsync(ulong guildId, ulong channelId, UploadFileParams args, RequestOptions options = null) - { - Preconditions.NotNull(args, nameof(args)); - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - - if (args._content.GetValueOrDefault(null) == null) - args._content = ""; - else if (args._content.IsSpecified) - { - 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)); - } - - if (guildId != 0) - return await SendMultipartAsync("POST", $"channels/{channelId}/messages", args.ToDictionary(), GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false); - else - return await SendMultipartAsync("POST", $"channels/{channelId}/messages", args.ToDictionary(), GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); - } - public Task DeleteMessageAsync(ulong guildId, ulong channelId, ulong messageId, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - - return DeleteMessageInternalAsync(guildId, channelId, messageId); - } - public Task DeleteDMMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) - { - return DeleteMessageInternalAsync(0, channelId, messageId); - } - private async Task DeleteMessageInternalAsync(ulong guildId, ulong channelId, ulong messageId, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - - if (guildId != 0) - await SendAsync("DELETE", $"channels/{channelId}/messages/{messageId}", GuildBucket.DeleteMessage, guildId, options: options).ConfigureAwait(false); - else - await SendAsync("DELETE", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); - } - public Task DeleteMessagesAsync(ulong guildId, ulong channelId, DeleteMessagesParams args, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - - return DeleteMessagesInternalAsync(guildId, channelId, args); - } - public Task DeleteDMMessagesAsync(ulong channelId, DeleteMessagesParams args, RequestOptions options = null) - { - return DeleteMessagesInternalAsync(0, channelId, args); - } - private async Task DeleteMessagesInternalAsync(ulong guildId, ulong channelId, DeleteMessagesParams args, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotNull(args, nameof(args)); - - var messageIds = args._messages; - Preconditions.NotNull(args._messages, nameof(args.MessageIds)); - Preconditions.AtMost(messageIds.Length, 100, nameof(messageIds.Length)); - - switch (messageIds.Length) - { - case 0: - return; - case 1: - await DeleteMessageInternalAsync(guildId, channelId, messageIds[0]).ConfigureAwait(false); - break; - default: - if (guildId != 0) - await SendAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, GuildBucket.DeleteMessages, guildId, options: options).ConfigureAwait(false); - else - await SendAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, options: options).ConfigureAwait(false); - break; - } - } - public Task ModifyMessageAsync(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - - return ModifyMessageInternalAsync(guildId, channelId, messageId, args); - } - public Task ModifyDMMessageAsync(ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) - { - return ModifyMessageInternalAsync(0, channelId, messageId, args); - } - private async Task ModifyMessageInternalAsync(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - Preconditions.NotNull(args, nameof(args)); - if (args._content.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)); - } - - if (guildId != 0) - return await SendAsync("PATCH", $"channels/{channelId}/messages/{messageId}", args, GuildBucket.SendEditMessage, guildId, options: options).ConfigureAwait(false); - else - return await SendAsync("PATCH", $"channels/{channelId}/messages/{messageId}", args, options: options).ConfigureAwait(false); - } - public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - - await SendAsync("POST", $"channels/{channelId}/messages/{messageId}/ack", options: options).ConfigureAwait(false); - } - public async Task TriggerTypingIndicatorAsync(ulong channelId, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - - await SendAsync("POST", $"channels/{channelId}/typing", options: options).ConfigureAwait(false); - } - //Users public async Task GetUserAsync(ulong userId, RequestOptions options = null) { @@ -1012,7 +867,7 @@ namespace Discord.API public async Task ModifySelfAsync(ModifyCurrentUserParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); - Preconditions.NotNullOrEmpty(args._username, nameof(args.Username)); + Preconditions.NotNullOrEmpty(args.Username, nameof(args.Username)); return await SendAsync("PATCH", "users/@me", args, options: options).ConfigureAwait(false); } @@ -1026,7 +881,7 @@ namespace Discord.API public async Task CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); - Preconditions.GreaterThan(args._recipientId, 0, nameof(args.Recipient)); + Preconditions.GreaterThan(args.RecipientId, 0, nameof(args.RecipientId)); return await SendAsync("POST", $"users/@me/channels", args, options: options).ConfigureAwait(false); } diff --git a/src/Discord.Net/API/Image.cs b/src/Discord.Net.Core/API/Image.cs similarity index 93% rename from src/Discord.Net/API/Image.cs rename to src/Discord.Net.Core/API/Image.cs index b2357a0a6..5442bd30f 100644 --- a/src/Discord.Net/API/Image.cs +++ b/src/Discord.Net.Core/API/Image.cs @@ -2,7 +2,7 @@ namespace Discord.API { - internal struct Image + public struct Image { public Stream Stream { get; } public string Hash { get; } diff --git a/src/Discord.Net/API/Int53Attribute.cs b/src/Discord.Net.Core/API/Int53Attribute.cs similarity index 100% rename from src/Discord.Net/API/Int53Attribute.cs rename to src/Discord.Net.Core/API/Int53Attribute.cs diff --git a/src/Discord.Net.Core/API/Rest/CreateChannelInviteParams.cs b/src/Discord.Net.Core/API/Rest/CreateChannelInviteParams.cs new file mode 100644 index 000000000..8a619a8b7 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/CreateChannelInviteParams.cs @@ -0,0 +1,16 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public 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; } + } +} diff --git a/src/Discord.Net/API/Rest/CreateDMChannelParams.cs b/src/Discord.Net.Core/API/Rest/CreateDMChannelParams.cs similarity index 56% rename from src/Discord.Net/API/Rest/CreateDMChannelParams.cs rename to src/Discord.Net.Core/API/Rest/CreateDMChannelParams.cs index f52b20ca1..83fe76e98 100644 --- a/src/Discord.Net/API/Rest/CreateDMChannelParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateDMChannelParams.cs @@ -7,8 +7,11 @@ namespace Discord.API.Rest public class CreateDMChannelParams { [JsonProperty("recipient_id")] - internal ulong _recipientId { get; set; } - public ulong RecipientId { set { _recipientId = value; } } - public IUser Recipient { set { _recipientId = value.Id; } } + public ulong RecipientId { get; } + + public CreateDMChannelParams(ulong recipientId) + { + RecipientId = recipientId; + } } } diff --git a/src/Discord.Net/API/Rest/CreateGuildBanParams.cs b/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs similarity index 64% rename from src/Discord.Net/API/Rest/CreateGuildBanParams.cs rename to src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs index 3b8fce473..9c049e8a8 100644 --- a/src/Discord.Net/API/Rest/CreateGuildBanParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs @@ -7,7 +7,6 @@ namespace Discord.API.Rest public class CreateGuildBanParams { [JsonProperty("delete-message-days")] - internal Optional _deleteMessageDays { get; set; } - public int DeleteMessageDays { set { _deleteMessageDays = value; } } + public Optional DeleteMessageDays { get; set; } } } diff --git a/src/Discord.Net.Core/API/Rest/CreateGuildChannelParams.cs b/src/Discord.Net.Core/API/Rest/CreateGuildChannelParams.cs new file mode 100644 index 000000000..f0e06e3d2 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/CreateGuildChannelParams.cs @@ -0,0 +1,23 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class CreateGuildChannelParams + { + [JsonProperty("name")] + public string Name { get; } + [JsonProperty("type")] + public ChannelType Type { get; } + + [JsonProperty("bitrate")] + public Optional Bitrate { get; set; } + + public CreateGuildChannelParams(string name, ChannelType type) + { + Name = name; + Type = type; + } + } +} diff --git a/src/Discord.Net/API/Rest/CreateGuildIntegrationParams.cs b/src/Discord.Net.Core/API/Rest/CreateGuildIntegrationParams.cs similarity index 57% rename from src/Discord.Net/API/Rest/CreateGuildIntegrationParams.cs rename to src/Discord.Net.Core/API/Rest/CreateGuildIntegrationParams.cs index 8e8dfb76d..0d6e3a654 100644 --- a/src/Discord.Net/API/Rest/CreateGuildIntegrationParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateGuildIntegrationParams.cs @@ -7,9 +7,14 @@ namespace Discord.API.Rest public class CreateGuildIntegrationParams { [JsonProperty("id")] - public ulong Id { internal get; set; } - + public ulong Id { get; } [JsonProperty("type")] - public string Type { internal get; set; } + public string Type { get; } + + public CreateGuildIntegrationParams(ulong id, string type) + { + Id = id; + Type = type; + } } } diff --git a/src/Discord.Net/API/Rest/CreateGuildParams.cs b/src/Discord.Net.Core/API/Rest/CreateGuildParams.cs similarity index 52% rename from src/Discord.Net/API/Rest/CreateGuildParams.cs rename to src/Discord.Net.Core/API/Rest/CreateGuildParams.cs index 8b9bd6178..4bc18c28b 100644 --- a/src/Discord.Net/API/Rest/CreateGuildParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateGuildParams.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 using Newtonsoft.Json; -using System.IO; namespace Discord.API.Rest { @@ -8,13 +7,17 @@ namespace Discord.API.Rest public class CreateGuildParams { [JsonProperty("name")] - public string Name { internal get; set; } - + public string Name { get; } [JsonProperty("region")] - public string Region { internal get; set; } + public string RegionId { get; } [JsonProperty("icon")] - internal Optional _icon { get; set; } - public Stream Icon { set { _icon = value != null ? new Image(value) : (Image?)null; } } + public Optional Icon { get; set; } + + public CreateGuildParams(string name, string regionId) + { + Name = name; + RegionId = regionId; + } } } diff --git a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs new file mode 100644 index 000000000..9577ab579 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs @@ -0,0 +1,22 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class CreateMessageParams + { + [JsonProperty("content")] + public string Content { get; } + + [JsonProperty("nonce")] + public Optional Nonce { get; set; } + [JsonProperty("tts")] + public Optional IsTTS { get; set; } + + public CreateMessageParams(string content) + { + Content = content; + } + } +} diff --git a/src/Discord.Net.Core/API/Rest/DeleteMessagesParams.cs b/src/Discord.Net.Core/API/Rest/DeleteMessagesParams.cs new file mode 100644 index 000000000..09b9a2bf1 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/DeleteMessagesParams.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class DeleteMessagesParams + { + [JsonProperty("messages")] + public ulong[] MessageIds { get; } + + public DeleteMessagesParams(ulong[] messageIds) + { + MessageIds = messageIds; + } + } +} diff --git a/src/Discord.Net.Core/API/Rest/GetChannelMessagesParams.cs b/src/Discord.Net.Core/API/Rest/GetChannelMessagesParams.cs new file mode 100644 index 000000000..2d00833ca --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/GetChannelMessagesParams.cs @@ -0,0 +1,10 @@ +#pragma warning disable CS1591 +namespace Discord.API.Rest +{ + public class GetChannelMessagesParams + { + public Optional Limit { get; set; } + public Optional RelativeDirection { get; set; } + public Optional RelativeMessageId { get; set; } + } +} diff --git a/src/Discord.Net/API/Rest/GetGatewayResponse.cs b/src/Discord.Net.Core/API/Rest/GetGatewayResponse.cs similarity index 100% rename from src/Discord.Net/API/Rest/GetGatewayResponse.cs rename to src/Discord.Net.Core/API/Rest/GetGatewayResponse.cs diff --git a/src/Discord.Net.Core/API/Rest/GetGuildMembersParams.cs b/src/Discord.Net.Core/API/Rest/GetGuildMembersParams.cs new file mode 100644 index 000000000..2bd34ddcb --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/GetGuildMembersParams.cs @@ -0,0 +1,9 @@ +#pragma warning disable CS1591 +namespace Discord.API.Rest +{ + public class GetGuildMembersParams + { + public Optional Limit { get; set; } + public Optional AfterUserId { get; set; } + } +} diff --git a/src/Discord.Net/API/Rest/GetGuildPruneCountResponse.cs b/src/Discord.Net.Core/API/Rest/GetGuildPruneCountResponse.cs similarity index 100% rename from src/Discord.Net/API/Rest/GetGuildPruneCountResponse.cs rename to src/Discord.Net.Core/API/Rest/GetGuildPruneCountResponse.cs diff --git a/src/Discord.Net/API/Rest/GuildPruneParams.cs b/src/Discord.Net.Core/API/Rest/GuildPruneParams.cs similarity index 65% rename from src/Discord.Net/API/Rest/GuildPruneParams.cs rename to src/Discord.Net.Core/API/Rest/GuildPruneParams.cs index 9b8b3d6e1..9cff46992 100644 --- a/src/Discord.Net/API/Rest/GuildPruneParams.cs +++ b/src/Discord.Net.Core/API/Rest/GuildPruneParams.cs @@ -7,6 +7,11 @@ namespace Discord.API.Rest public class GuildPruneParams { [JsonProperty("days")] - public int Days { internal get; set; } + public int Days { get; } + + public GuildPruneParams(int days) + { + Days = days; + } } } diff --git a/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs b/src/Discord.Net.Core/API/Rest/ModifyChannelPermissionsParams.cs similarity index 51% rename from src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyChannelPermissionsParams.cs index a650eeefa..8676b22e7 100644 --- a/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyChannelPermissionsParams.cs @@ -7,10 +7,17 @@ namespace Discord.API.Rest public class ModifyChannelPermissionsParams { [JsonProperty("type")] - public string Type { internal get; set; } + public string Type { get; } [JsonProperty("allow")] - public ulong Allow { internal get; set; } + public ulong Allow { get; } [JsonProperty("deny")] - public ulong Deny { internal get; set; } + public ulong Deny { get; } + + public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny) + { + Type = type; + Allow = allow; + Deny = deny; + } } } diff --git a/src/Discord.Net/API/Rest/ModifyCurrentUserNickParams.cs b/src/Discord.Net.Core/API/Rest/ModifyCurrentUserNickParams.cs similarity index 61% rename from src/Discord.Net/API/Rest/ModifyCurrentUserNickParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyCurrentUserNickParams.cs index dd6fba46e..ca7ad2bd3 100644 --- a/src/Discord.Net/API/Rest/ModifyCurrentUserNickParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyCurrentUserNickParams.cs @@ -7,6 +7,11 @@ namespace Discord.API.Rest public class ModifyCurrentUserNickParams { [JsonProperty("nick")] - public string Nickname { internal get; set; } + public string Nickname { get; } + + public ModifyCurrentUserNickParams(string nickname) + { + Nickname = nickname; + } } } diff --git a/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs b/src/Discord.Net.Core/API/Rest/ModifyCurrentUserParams.cs similarity index 51% rename from src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyCurrentUserParams.cs index 732e46377..d11ef2b77 100644 --- a/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyCurrentUserParams.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 using Newtonsoft.Json; -using System.IO; namespace Discord.API.Rest { @@ -8,11 +7,8 @@ namespace Discord.API.Rest public class ModifyCurrentUserParams { [JsonProperty("username")] - internal Optional _username { get; set; } - public string Username { set { _username = value; } } - + public Optional Username { get; set; } [JsonProperty("avatar")] - internal Optional _avatar { get; set; } - public Stream Avatar { set { _avatar = new Image(value); } } + public Optional Avatar { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildChannelParams.cs b/src/Discord.Net.Core/API/Rest/ModifyGuildChannelParams.cs similarity index 55% rename from src/Discord.Net/API/Rest/ModifyGuildChannelParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyGuildChannelParams.cs index 868150f87..6d6ee4c24 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildChannelParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyGuildChannelParams.cs @@ -7,11 +7,8 @@ namespace Discord.API.Rest public class ModifyGuildChannelParams { [JsonProperty("name")] - internal Optional _name { get; set; } - public string Name { set { _name = value; } } - + public Optional Name { get; set; } [JsonProperty("position")] - internal Optional _position { get; set; } - public int Position { set { _position = value; } } + public Optional Position { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs b/src/Discord.Net.Core/API/Rest/ModifyGuildChannelsParams.cs similarity index 55% rename from src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyGuildChannelsParams.cs index 99679b924..8ac3299fa 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyGuildChannelsParams.cs @@ -7,9 +7,14 @@ namespace Discord.API.Rest public class ModifyGuildChannelsParams { [JsonProperty("id")] - public ulong Id { internal get; set; } - + public ulong Id { get; set; } [JsonProperty("position")] - public int Position { internal get; set; } + public int Position { get; set; } + + public ModifyGuildChannelsParams(ulong id, int position) + { + Id = id; + Position = position; + } } } diff --git a/src/Discord.Net.Core/API/Rest/ModifyGuildEmbedParams.cs b/src/Discord.Net.Core/API/Rest/ModifyGuildEmbedParams.cs new file mode 100644 index 000000000..f362f8cd7 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/ModifyGuildEmbedParams.cs @@ -0,0 +1,14 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class ModifyGuildEmbedParams + { + [JsonProperty("enabled")] + public Optional Enabled { get; set; } + [JsonProperty("channel")] + public Optional ChannelId { get; set; } + } +} diff --git a/src/Discord.Net.Core/API/Rest/ModifyGuildIntegrationParams.cs b/src/Discord.Net.Core/API/Rest/ModifyGuildIntegrationParams.cs new file mode 100644 index 000000000..3a5526c96 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/ModifyGuildIntegrationParams.cs @@ -0,0 +1,16 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public 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; } + } +} diff --git a/src/Discord.Net.Core/API/Rest/ModifyGuildMemberParams.cs b/src/Discord.Net.Core/API/Rest/ModifyGuildMemberParams.cs new file mode 100644 index 000000000..17a8e2da1 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/ModifyGuildMemberParams.cs @@ -0,0 +1,20 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public 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; } + } +} diff --git a/src/Discord.Net.Core/API/Rest/ModifyGuildParams.cs b/src/Discord.Net.Core/API/Rest/ModifyGuildParams.cs new file mode 100644 index 000000000..f72ff2c96 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/ModifyGuildParams.cs @@ -0,0 +1,30 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public 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("default_message_notifications")] + public Optional DefaultMessageNotifications { get; set; } + [JsonProperty("afk_timeout")] + public Optional AfkTimeout { 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.Core/API/Rest/ModifyGuildRoleParams.cs b/src/Discord.Net.Core/API/Rest/ModifyGuildRoleParams.cs new file mode 100644 index 000000000..d1226b534 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/ModifyGuildRoleParams.cs @@ -0,0 +1,20 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class ModifyGuildRoleParams + { + [JsonProperty("name")] + public Optional Name { get; set; } + [JsonProperty("permissions")] + public Optional Permissions { get; set; } + [JsonProperty("position")] + public Optional Position { get; set; } + [JsonProperty("color")] + public Optional Color { get; set; } + [JsonProperty("hoist")] + public Optional Hoist { get; set; } + } +} diff --git a/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs b/src/Discord.Net.Core/API/Rest/ModifyGuildRolesParams.cs similarity index 67% rename from src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyGuildRolesParams.cs index 69cd413e4..2350a8c47 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyGuildRolesParams.cs @@ -7,6 +7,11 @@ namespace Discord.API.Rest public class ModifyGuildRolesParams : ModifyGuildRoleParams { [JsonProperty("id")] - public ulong Id { internal get; set; } + public ulong Id { get; } + + public ModifyGuildRolesParams(ulong id) + { + Id = id; + } } } diff --git a/src/Discord.Net/API/Rest/ModifyMessageParams.cs b/src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs similarity index 67% rename from src/Discord.Net/API/Rest/ModifyMessageParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs index aca058ff4..4901ddc9d 100644 --- a/src/Discord.Net/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs @@ -7,7 +7,6 @@ namespace Discord.API.Rest public class ModifyMessageParams { [JsonProperty("content")] - internal Optional _content { get; set; } - public string Content { set { _content = value; } } + public Optional Content { get; set; } } } diff --git a/src/Discord.Net.Core/API/Rest/ModifyPresenceParams.cs b/src/Discord.Net.Core/API/Rest/ModifyPresenceParams.cs new file mode 100644 index 000000000..52145643a --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/ModifyPresenceParams.cs @@ -0,0 +1,9 @@ +#pragma warning disable CS1591 +namespace Discord.API.Rest +{ + public class ModifyPresenceParams + { + public Optional Status { get; set; } + public Optional Game { get; set; } + } +} diff --git a/src/Discord.Net/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Core/API/Rest/ModifyTextChannelParams.cs similarity index 70% rename from src/Discord.Net/API/Rest/ModifyTextChannelParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyTextChannelParams.cs index 783771212..3546cee95 100644 --- a/src/Discord.Net/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyTextChannelParams.cs @@ -7,7 +7,6 @@ namespace Discord.API.Rest public class ModifyTextChannelParams : ModifyGuildChannelParams { [JsonProperty("topic")] - internal Optional _topic { get; set; } - public string Topic { set { _topic = value; } } + public Optional Topic { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs b/src/Discord.Net.Core/API/Rest/ModifyVoiceChannelParams.cs similarity index 57% rename from src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs rename to src/Discord.Net.Core/API/Rest/ModifyVoiceChannelParams.cs index 5732feceb..8b5af9d8e 100644 --- a/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyVoiceChannelParams.cs @@ -7,11 +7,8 @@ namespace Discord.API.Rest public class ModifyVoiceChannelParams : ModifyGuildChannelParams { [JsonProperty("bitrate")] - internal Optional _bitrate { get; set; } - public int Bitrate { set { _bitrate = value; } } - + public Optional Bitrate { get; set; } [JsonProperty("user_limit")] - internal Optional _userLimit { get; set; } - public int UserLimit { set { _userLimit = value; } } + public Optional UserLimit { get; set; } } } diff --git a/src/Discord.Net.Core/API/Rest/UploadFileParams.cs b/src/Discord.Net.Core/API/Rest/UploadFileParams.cs new file mode 100644 index 000000000..bbd798900 --- /dev/null +++ b/src/Discord.Net.Core/API/Rest/UploadFileParams.cs @@ -0,0 +1,35 @@ +#pragma warning disable CS1591 +using Discord.Net.Rest; +using System.Collections.Generic; +using System.IO; + +namespace Discord.API.Rest +{ + public class UploadFileParams + { + public Stream File { get; } + + public Optional Filename { get; set; } + public Optional Content { get; set; } + public Optional Nonce { get; set; } + public Optional IsTTS { get; set; } + + public UploadFileParams(Stream file) + { + File = file; + } + + public IReadOnlyDictionary ToDictionary() + { + var d = new Dictionary(); + d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat")); + if (Content.IsSpecified) + d["content"] = Content.Value; + if (IsTTS.IsSpecified) + d["tts"] = IsTTS.Value.ToString(); + if (Nonce.IsSpecified) + d["nonce"] = Nonce.Value; + return d; + } + } +} diff --git a/src/Discord.Net/API/WebSocketMessage.cs b/src/Discord.Net.Core/API/WebSocketMessage.cs similarity index 100% rename from src/Discord.Net/API/WebSocketMessage.cs rename to src/Discord.Net.Core/API/WebSocketMessage.cs diff --git a/src/Discord.Net/Audio/IAudioClient.cs b/src/Discord.Net.Core/Audio/IAudioClient.cs similarity index 65% rename from src/Discord.Net/Audio/IAudioClient.cs rename to src/Discord.Net.Core/Audio/IAudioClient.cs index 312152142..3cfdfa856 100644 --- a/src/Discord.Net/Audio/IAudioClient.cs +++ b/src/Discord.Net.Core/Audio/IAudioClient.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Threading.Tasks; namespace Discord.Audio @@ -8,8 +9,7 @@ namespace Discord.Audio event Func Connected; event Func Disconnected; event Func LatencyUpdated; - - DiscordVoiceAPIClient ApiClient { get; } + /// Gets the current connection state of this client. ConnectionState ConnectionState { get; } /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. @@ -17,7 +17,7 @@ namespace Discord.Audio Task DisconnectAsync(); - RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); - OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); + Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); + Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); } } diff --git a/src/Discord.Net/Audio/Opus/OpusApplication.cs b/src/Discord.Net.Core/Audio/Opus/OpusApplication.cs similarity index 100% rename from src/Discord.Net/Audio/Opus/OpusApplication.cs rename to src/Discord.Net.Core/Audio/Opus/OpusApplication.cs diff --git a/src/Discord.Net/ConnectionState.cs b/src/Discord.Net.Core/ConnectionState.cs similarity index 100% rename from src/Discord.Net/ConnectionState.cs rename to src/Discord.Net.Core/ConnectionState.cs diff --git a/src/Discord.Net.Core/Discord.Net.Core.xproj b/src/Discord.Net.Core/Discord.Net.Core.xproj new file mode 100644 index 000000000..6759e09b4 --- /dev/null +++ b/src/Discord.Net.Core/Discord.Net.Core.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 91e9e7bd-75c9-4e98-84aa-2c271922e5c2 + Discord + .\obj + .\bin\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/Discord.Net/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs similarity index 100% rename from src/Discord.Net/DiscordConfig.cs rename to src/Discord.Net.Core/DiscordConfig.cs diff --git a/src/Discord.Net/Entities/Channels/ChannelType.cs b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs similarity index 100% rename from src/Discord.Net/Entities/Channels/ChannelType.cs rename to src/Discord.Net.Core/Entities/Channels/ChannelType.cs diff --git a/src/Discord.Net/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs similarity index 57% rename from src/Discord.Net/Entities/Channels/IChannel.cs rename to src/Discord.Net.Core/Entities/Channels/IChannel.cs index 53c681b56..e3990204c 100644 --- a/src/Discord.Net/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -3,11 +3,15 @@ using System.Threading.Tasks; namespace Discord { - public interface IChannel : ISnowflakeEntity, IUpdateable + public interface IChannel : ISnowflakeEntity { + IReadOnlyCollection CachedUsers { get; } + /// Gets a collection of all users in this channel. - Task> GetUsersAsync(); + IAsyncEnumerable> GetUsersAsync(); + /// Gets a user in this channel with the provided id. Task GetUserAsync(ulong id); + IUser GetCachedUser(ulong id); } } diff --git a/src/Discord.Net/Entities/Channels/IDMChannel.cs b/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs similarity index 100% rename from src/Discord.Net/Entities/Channels/IDMChannel.cs rename to src/Discord.Net.Core/Entities/Channels/IDMChannel.cs diff --git a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs new file mode 100644 index 000000000..dafc997a7 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + public interface IGroupChannel : IMessageChannel, IPrivateChannel + { + ///// Adds a user to this group. + //Task AddUserAsync(IUser user); + + //new IReadOnlyCollection CachedUsers { get; } + + /// Leaves this group. + Task LeaveAsync(); + } +} \ No newline at end of file diff --git a/src/Discord.Net/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs similarity index 89% rename from src/Discord.Net/Entities/Channels/IGuildChannel.cs rename to src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index e42ace8d6..0927c2b79 100644 --- a/src/Discord.Net/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -12,8 +12,9 @@ namespace Discord /// Gets the position of this channel in the guild's channel list, relative to others of the same type. int Position { get; } - /// 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; } + new IReadOnlyCollection CachedUsers { get; } /// Creates a new invite to this channel. /// The time (in seconds) until the invite expires. Set to null to never expire. @@ -43,8 +44,9 @@ namespace Discord Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions); /// Gets a collection of all users in this channel. - new Task> GetUsersAsync(); + new IAsyncEnumerable> GetUsersAsync(); /// Gets a user in this channel with the provided id. new Task GetUserAsync(ulong id); + new IGuildUser GetCachedUser(ulong id); } } \ No newline at end of file diff --git a/src/Discord.Net/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs similarity index 82% rename from src/Discord.Net/Entities/Channels/IMessageChannel.cs rename to src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index a2288b0ee..e8089d2af 100644 --- a/src/Discord.Net/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -15,20 +16,21 @@ namespace Discord Task SendFileAsync(string filePath, string text = null, bool isTTS = false); /// Sends a file to this text channel, with an optional caption. Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); + /// Gets a message from this message channel with the given id, or null if not found. Task GetMessageAsync(ulong id); /// Gets the message from this channel's cache with the given id, or null if not found. IMessage GetCachedMessage(ulong id); /// Gets the last N messages from this message channel. - Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); + IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of messages in this channel. - Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of pinned messages in this channel. Task> GetPinnedMessagesAsync(); /// Bulk deletes multiple messages. Task DeleteMessagesAsync(IEnumerable messages); /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. - Task TriggerTypingAsync(); + IDisposable EnterTypingState(); } } diff --git a/src/Discord.Net/Entities/Channels/IPrivateChannel.cs b/src/Discord.Net.Core/Entities/Channels/IPrivateChannel.cs similarity index 100% rename from src/Discord.Net/Entities/Channels/IPrivateChannel.cs rename to src/Discord.Net.Core/Entities/Channels/IPrivateChannel.cs diff --git a/src/Discord.Net/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs similarity index 100% rename from src/Discord.Net/Entities/Channels/ITextChannel.cs rename to src/Discord.Net.Core/Entities/Channels/ITextChannel.cs diff --git a/src/Discord.Net/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs similarity index 100% rename from src/Discord.Net/Entities/Channels/IVoiceChannel.cs rename to src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs diff --git a/src/Discord.Net/Entities/Guilds/DefaultMessageNotifications.cs b/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs similarity index 100% rename from src/Discord.Net/Entities/Guilds/DefaultMessageNotifications.cs rename to src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs diff --git a/src/Discord.Net.Core/Entities/Guilds/Emoji.cs b/src/Discord.Net.Core/Entities/Guilds/Emoji.cs new file mode 100644 index 000000000..fc8b64fa4 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/Emoji.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Model = Discord.API.Emoji; + +namespace Discord +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public struct Emoji + { + public ulong Id { get; } + public string Name { get; } + public bool IsManaged { get; } + public bool RequireColons { get; } + public IReadOnlyList RoleIds { get; } + + public Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList roleIds) + { + Id = id; + Name = name; + IsManaged = isManaged; + RequireColons = requireColons; + RoleIds = roleIds; + } + public static Emoji Create(Model model) + { + return new Emoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); + } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/IBan.cs b/src/Discord.Net.Core/Entities/Guilds/IBan.cs new file mode 100644 index 000000000..aee2cc198 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/IBan.cs @@ -0,0 +1,13 @@ +//using Discord.Rest; +using System.Diagnostics; +using Model = Discord.API.Ban; + +namespace Discord +{ + + public interface IBan + { + IUser User { get; } + string Reason { get; } + } +} diff --git a/src/Discord.Net/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs similarity index 89% rename from src/Discord.Net/Entities/Guilds/IGuild.cs rename to src/Discord.Net.Core/Entities/Guilds/IGuild.cs index b1c010439..489f9e8f5 100644 --- a/src/Discord.Net/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -6,7 +6,7 @@ using Discord.Audio; namespace Discord { - public interface IGuild : IDeletable, ISnowflakeEntity, IUpdateable + public interface IGuild : IDeletable, ISnowflakeEntity { /// Gets the name of this guild. string Name { get; } @@ -20,8 +20,12 @@ namespace Discord 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. @@ -48,6 +52,7 @@ namespace Discord IReadOnlyCollection Features { get; } /// Gets a collection of all roles in this guild. IReadOnlyCollection Roles { get; } + IReadOnlyCollection CachedUsers { get; } /// Modifies this guild. Task ModifyAsync(Action func); @@ -61,7 +66,7 @@ namespace Discord Task LeaveAsync(); /// Gets a collection of all users banned on this guild. - Task> GetBansAsync(); + Task> GetBansAsync(); /// Bans the provided user from this guild and optionally prunes their recent messages. Task AddBanAsync(IUser user, int pruneDays = 0); /// Bans the provided user id from this guild and optionally prunes their recent messages. @@ -75,11 +80,15 @@ namespace Discord Task> GetChannelsAsync(); /// Gets the channel in this guild with the provided id, or null if not found. Task GetChannelAsync(ulong id); + IGuildChannel GetCachedChannel(ulong id); /// Creates a new text channel. Task CreateTextChannelAsync(string name); /// Creates a new voice channel. Task CreateVoiceChannelAsync(string name); + Task> GetIntegrationsAsync(); + Task CreateIntegrationAsync(ulong id, string type); + /// Gets a collection of all invites to this guild. Task> GetInvitesAsync(); @@ -92,9 +101,10 @@ namespace Discord Task> GetUsersAsync(); /// Gets the user in this guild with the provided id, or null if not found. Task GetUserAsync(ulong id); + IGuildUser GetCachedUser(ulong id); /// Gets the current user for this guild. Task GetCurrentUserAsync(); - /// Downloads all users for this guild if the current list is incomplete. Only applies to the WebSocket client. + /// 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. Task PruneUsersAsync(int days = 30, bool simulate = false); diff --git a/src/Discord.Net/Entities/Guilds/IGuildIntegration.cs b/src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs similarity index 83% rename from src/Discord.Net/Entities/Guilds/IGuildIntegration.cs rename to src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs index 7f6ed6408..1a0c6d2d0 100644 --- a/src/Discord.Net/Entities/Guilds/IGuildIntegration.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs @@ -2,7 +2,6 @@ namespace Discord { - //TODO: Add docstrings public interface IGuildIntegration { ulong Id { get; } @@ -15,8 +14,8 @@ namespace Discord DateTimeOffset SyncedAt { get; } IntegrationAccount Account { get; } - IGuild Guild { get; } + ulong GuildId { get; } + ulong RoleId { get; } IUser User { get; } - IRole Role { get; } } } diff --git a/src/Discord.Net/Entities/Guilds/IUserGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs similarity index 100% rename from src/Discord.Net/Entities/Guilds/IUserGuild.cs rename to src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs diff --git a/src/Discord.Net/Entities/Guilds/IVoiceRegion.cs b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs similarity index 100% rename from src/Discord.Net/Entities/Guilds/IVoiceRegion.cs rename to src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs diff --git a/src/Discord.Net/Entities/Guilds/IntegrationAccount.cs b/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs similarity index 100% rename from src/Discord.Net/Entities/Guilds/IntegrationAccount.cs rename to src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs diff --git a/src/Discord.Net/Entities/Guilds/MfaLevel.cs b/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs similarity index 100% rename from src/Discord.Net/Entities/Guilds/MfaLevel.cs rename to src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs diff --git a/src/Discord.Net/Entities/Guilds/VerificationLevel.cs b/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs similarity index 100% rename from src/Discord.Net/Entities/Guilds/VerificationLevel.cs rename to src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs diff --git a/src/Discord.Net/Entities/IApplication.cs b/src/Discord.Net.Core/Entities/IApplication.cs similarity index 77% rename from src/Discord.Net/Entities/IApplication.cs rename to src/Discord.Net.Core/Entities/IApplication.cs index 157205226..4fb1e4b91 100644 --- a/src/Discord.Net/Entities/IApplication.cs +++ b/src/Discord.Net.Core/Entities/IApplication.cs @@ -1,6 +1,6 @@ namespace Discord { - public interface IApplication : ISnowflakeEntity, IUpdateable + public interface IApplication : ISnowflakeEntity { string Name { get; } string Description { get; } diff --git a/src/Discord.Net/Entities/IDeletable.cs b/src/Discord.Net.Core/Entities/IDeletable.cs similarity index 100% rename from src/Discord.Net/Entities/IDeletable.cs rename to src/Discord.Net.Core/Entities/IDeletable.cs diff --git a/src/Discord.Net.Core/Entities/IEntity.cs b/src/Discord.Net.Core/Entities/IEntity.cs new file mode 100644 index 000000000..b1bb922c9 --- /dev/null +++ b/src/Discord.Net.Core/Entities/IEntity.cs @@ -0,0 +1,15 @@ +using System; + +namespace Discord +{ + public interface IEntity + where TId : IEquatable + { + /// Gets the IDiscordClient that created this object. + IDiscordClient Discord { get; } + + /// Gets the unique identifier for this object. + TId Id { get; } + + } +} diff --git a/src/Discord.Net/Entities/IMentionable.cs b/src/Discord.Net.Core/Entities/IMentionable.cs similarity index 100% rename from src/Discord.Net/Entities/IMentionable.cs rename to src/Discord.Net.Core/Entities/IMentionable.cs diff --git a/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs b/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs new file mode 100644 index 000000000..6eb947bf8 --- /dev/null +++ b/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs @@ -0,0 +1,6 @@ +namespace Discord +{ + public interface ISnowflakeEntity : IEntity + { + } +} diff --git a/src/Discord.Net/Entities/IUpdateable.cs b/src/Discord.Net.Core/Entities/IUpdateable.cs similarity index 100% rename from src/Discord.Net/Entities/IUpdateable.cs rename to src/Discord.Net.Core/Entities/IUpdateable.cs diff --git a/src/Discord.Net/Entities/Invites/IInvite.cs b/src/Discord.Net.Core/Entities/Invites/IInvite.cs similarity index 100% rename from src/Discord.Net/Entities/Invites/IInvite.cs rename to src/Discord.Net.Core/Entities/Invites/IInvite.cs diff --git a/src/Discord.Net/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs similarity index 100% rename from src/Discord.Net/Entities/Invites/IInviteMetadata.cs rename to src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs diff --git a/src/Discord.Net/Entities/Messages/Direction.cs b/src/Discord.Net.Core/Entities/Messages/Direction.cs similarity index 100% rename from src/Discord.Net/Entities/Messages/Direction.cs rename to src/Discord.Net.Core/Entities/Messages/Direction.cs diff --git a/src/Discord.Net/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs similarity index 69% rename from src/Discord.Net/Entities/Messages/EmbedProvider.cs rename to src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 1f1ef6d2d..4d4aa1ea8 100644 --- a/src/Discord.Net/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -12,7 +12,9 @@ namespace Discord Name = name; Url = url; } - internal EmbedProvider(Model model) - : this(model.Name, model.Url) { } + public static EmbedProvider Create(Model model) + { + return new EmbedProvider(model.Name, model.Url); + } } } diff --git a/src/Discord.Net/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs similarity index 81% rename from src/Discord.Net/Entities/Messages/EmbedThumbnail.cs rename to src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 736d7d743..8e1a2a527 100644 --- a/src/Discord.Net/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -16,14 +16,11 @@ namespace Discord Height = height; Width = width; } - - internal EmbedThumbnail(Model model) - : this( - model.Url, - model.ProxyUrl, - model.Height.IsSpecified ? model.Height.Value : (int?)null, - model.Width.IsSpecified ? model.Width.Value : (int?)null) + public static EmbedThumbnail Create(Model model) { + return new EmbedThumbnail(model.Url, model.ProxyUrl, + model.Height.IsSpecified ? model.Height.Value : (int?)null, + model.Width.IsSpecified ? model.Width.Value : (int?)null); } } } diff --git a/src/Discord.Net/Entities/Messages/IAttachment.cs b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs similarity index 100% rename from src/Discord.Net/Entities/Messages/IAttachment.cs rename to src/Discord.Net.Core/Entities/Messages/IAttachment.cs diff --git a/src/Discord.Net/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs similarity index 69% rename from src/Discord.Net/Entities/Messages/IEmbed.cs rename to src/Discord.Net.Core/Entities/Messages/IEmbed.cs index e0080f320..3bca85fd0 100644 --- a/src/Discord.Net/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -6,7 +6,7 @@ string Type { get; } string Title { get; } string Description { get; } - EmbedProvider Provider { get; } - EmbedThumbnail Thumbnail { get; } + EmbedProvider? Provider { get; } + EmbedThumbnail? Thumbnail { get; } } } diff --git a/src/Discord.Net/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs similarity index 85% rename from src/Discord.Net/Entities/Messages/IMessage.cs rename to src/Discord.Net.Core/Entities/Messages/IMessage.cs index 0c83b1024..be83e7482 100644 --- a/src/Discord.Net/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; namespace Discord { - public interface IMessage : ISnowflakeEntity, IUpdateable + public interface IMessage : ISnowflakeEntity { + /// Gets the type of this system message. + MessageType Type { 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. @@ -16,8 +18,8 @@ namespace Discord /// 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 id of the channel this message was sent to. + ulong ChannelId { get; } /// Gets the author of this message. IUser Author { get; } diff --git a/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs b/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs new file mode 100644 index 000000000..2dfaf8f2d --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/ISystemMessage.cs @@ -0,0 +1,6 @@ +namespace Discord +{ + public interface ISystemMessage : IMessage + { + } +} diff --git a/src/Discord.Net/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs similarity index 100% rename from src/Discord.Net/Entities/Messages/IUserMessage.cs rename to src/Discord.Net.Core/Entities/Messages/IUserMessage.cs diff --git a/src/Discord.Net/Entities/Messages/ChannelMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs similarity index 100% rename from src/Discord.Net/Entities/Messages/ChannelMentionHandling.cs rename to src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs diff --git a/src/Discord.Net/Entities/Messages/EveryoneMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs similarity index 100% rename from src/Discord.Net/Entities/Messages/EveryoneMentionHandling.cs rename to src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs diff --git a/src/Discord.Net/Entities/Messages/RoleMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs similarity index 100% rename from src/Discord.Net/Entities/Messages/RoleMentionHandling.cs rename to src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs diff --git a/src/Discord.Net/Entities/Messages/UserMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs similarity index 100% rename from src/Discord.Net/Entities/Messages/UserMentionHandling.cs rename to src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs diff --git a/src/Discord.Net/Entities/Messages/MessageType.cs b/src/Discord.Net.Core/Entities/Messages/MessageType.cs similarity index 100% rename from src/Discord.Net/Entities/Messages/MessageType.cs rename to src/Discord.Net.Core/Entities/Messages/MessageType.cs diff --git a/src/Discord.Net/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs similarity index 100% rename from src/Discord.Net/Entities/Permissions/ChannelPermission.cs rename to src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs diff --git a/src/Discord.Net/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs similarity index 100% rename from src/Discord.Net/Entities/Permissions/ChannelPermissions.cs rename to src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs diff --git a/src/Discord.Net/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs similarity index 100% rename from src/Discord.Net/Entities/Permissions/GuildPermission.cs rename to src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs diff --git a/src/Discord.Net/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs similarity index 100% rename from src/Discord.Net/Entities/Permissions/GuildPermissions.cs rename to src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs diff --git a/src/Discord.Net/Entities/Permissions/Overwrite.cs b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs similarity index 96% rename from src/Discord.Net/Entities/Permissions/Overwrite.cs rename to src/Discord.Net.Core/Entities/Permissions/Overwrite.cs index 7333d93e1..ff5b00623 100644 --- a/src/Discord.Net/Entities/Permissions/Overwrite.cs +++ b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs @@ -19,7 +19,7 @@ namespace Discord Permissions = permissions; } - internal Overwrite(Model model) + public Overwrite(Model model) : this(model.TargetId, model.TargetType, new OverwritePermissions(model.Allow, model.Deny)) { } } } diff --git a/src/Discord.Net/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs similarity index 100% rename from src/Discord.Net/Entities/Permissions/OverwritePermissions.cs rename to src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs diff --git a/src/Discord.Net/Entities/Permissions/PermValue.cs b/src/Discord.Net.Core/Entities/Permissions/PermValue.cs similarity index 100% rename from src/Discord.Net/Entities/Permissions/PermValue.cs rename to src/Discord.Net.Core/Entities/Permissions/PermValue.cs diff --git a/src/Discord.Net/Entities/Permissions/PermissionTarget.cs b/src/Discord.Net.Core/Entities/Permissions/PermissionTarget.cs similarity index 100% rename from src/Discord.Net/Entities/Permissions/PermissionTarget.cs rename to src/Discord.Net.Core/Entities/Permissions/PermissionTarget.cs diff --git a/src/Discord.Net/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs similarity index 100% rename from src/Discord.Net/Entities/Roles/Color.cs rename to src/Discord.Net.Core/Entities/Roles/Color.cs diff --git a/src/Discord.Net/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs similarity index 64% rename from src/Discord.Net/Entities/Roles/IRole.cs rename to src/Discord.Net.Core/Entities/Roles/IRole.cs index 29975be46..bcea9b17e 100644 --- a/src/Discord.Net/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Discord.API.Rest; - -namespace Discord +namespace Discord { - public interface IRole : IDeletable, ISnowflakeEntity + public interface IRole : ISnowflakeEntity, IDeletable, IMentionable { + /// Gets the guild owning this role. + IGuild Guild { get; } + /// 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. @@ -20,10 +18,7 @@ namespace Discord /// Gets this role's position relative to other roles in the same guild. int Position { get; } - /// Gets the id of the guild owning this role. - ulong GuildId { get; } - - /// Modifies this role. - Task ModifyAsync(Action func); + ///// Modifies this role. + //Task ModifyAsync(Action func); } } \ No newline at end of file diff --git a/src/Discord.Net/Entities/Users/Game.cs b/src/Discord.Net.Core/Entities/Users/Game.cs similarity index 71% rename from src/Discord.Net/Entities/Users/Game.cs rename to src/Discord.Net.Core/Entities/Users/Game.cs index 9b5d891ef..2a8645df3 100644 --- a/src/Discord.Net/Entities/Users/Game.cs +++ b/src/Discord.Net.Core/Entities/Users/Game.cs @@ -4,7 +4,7 @@ using Model = Discord.API.Game; namespace Discord { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class Game + public struct Game { public string Name { get; } public string StreamUrl { get; } @@ -18,8 +18,12 @@ namespace Discord } public Game(string name) : this(name, null, StreamType.NotStreaming) { } - internal Game(Model model) - : this(model.Name, model.StreamUrl.GetValueOrDefault(null), model.StreamType.GetValueOrDefault(null) ?? StreamType.NotStreaming) { } + public static Game Create(Model model) + { + return new Game(model.Name, + model.StreamUrl.GetValueOrDefault(null), + model.StreamType.GetValueOrDefault(null) ?? StreamType.NotStreaming); + } public override string ToString() => Name; private string DebuggerDisplay => StreamUrl != null ? $"{Name} ({StreamUrl})" : Name; diff --git a/src/Discord.Net/Entities/Users/IConnection.cs b/src/Discord.Net.Core/Entities/Users/IConnection.cs similarity index 100% rename from src/Discord.Net/Entities/Users/IConnection.cs rename to src/Discord.Net.Core/Entities/Users/IConnection.cs diff --git a/src/Discord.Net.Core/Entities/Users/IGroupUser.cs b/src/Discord.Net.Core/Entities/Users/IGroupUser.cs new file mode 100644 index 000000000..8b4c3bf70 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Users/IGroupUser.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Discord +{ + public interface IGroupUser : IUser, IVoiceState + { + ///// Kicks this user from this group. + //Task KickAsync(); + } +} diff --git a/src/Discord.Net/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs similarity index 53% rename from src/Discord.Net/Entities/Users/IGuildUser.cs rename to src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 3115b246a..b109e2f31 100644 --- a/src/Discord.Net/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -6,19 +6,17 @@ using Discord.API.Rest; namespace Discord { /// A Guild-User pairing. - public interface IGuildUser : IUpdateable, IUser, IVoiceState + public interface IGuildUser : IUser, IVoiceState { /// Gets when this user joined this guild. DateTimeOffset? JoinedAt { get; } /// Gets the nickname for this user. string Nickname { get; } - /// Gets the guild-level permissions granted to this user by their roles. - GuildPermissions GuildPermissions { get; } - /// Gets the guild for this guild-user pair. - IGuild Guild { get; } - /// Returns a collection of the roles this user is a member of in this guild, including the guild's @everyone role. - IReadOnlyCollection Roles { 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. + IReadOnlyCollection RoleIds { get; } /// Gets the level permissions granted to this user to a given channel. ChannelPermissions GetPermissions(IGuildChannel channel); @@ -27,8 +25,5 @@ namespace Discord Task KickAsync(); /// Modifies this user's properties in this guild. Task ModifyAsync(Action func); - - /// Returns a private message channel to this user, creating one if it does not already exist. - Task CreateDMChannelAsync(); } } diff --git a/src/Discord.Net/Entities/Users/IPresence.cs b/src/Discord.Net.Core/Entities/Users/IPresence.cs similarity index 89% rename from src/Discord.Net/Entities/Users/IPresence.cs rename to src/Discord.Net.Core/Entities/Users/IPresence.cs index af7be998a..7f182241b 100644 --- a/src/Discord.Net/Entities/Users/IPresence.cs +++ b/src/Discord.Net.Core/Entities/Users/IPresence.cs @@ -3,7 +3,7 @@ public interface IPresence { /// Gets the game this user is currently playing, if any. - Game Game { get; } + Game? Game { get; } /// Gets the current status of this user. UserStatus Status { get; } } diff --git a/src/Discord.Net/Entities/Users/ISelfUser.cs b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs similarity index 91% rename from src/Discord.Net/Entities/Users/ISelfUser.cs rename to src/Discord.Net.Core/Entities/Users/ISelfUser.cs index b6803ccf6..dea95b566 100644 --- a/src/Discord.Net/Entities/Users/ISelfUser.cs +++ b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace Discord { - public interface ISelfUser : IUser, IUpdateable + public interface ISelfUser : IUser { /// Gets the email associated with this user. string Email { get; } diff --git a/src/Discord.Net/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs similarity index 57% rename from src/Discord.Net/Entities/Users/IUser.cs rename to src/Discord.Net.Core/Entities/Users/IUser.cs index 5eef8231c..30aa974cf 100644 --- a/src/Discord.Net/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -1,7 +1,11 @@ +using System.Threading.Tasks; + namespace Discord { public interface IUser : ISnowflakeEntity, IMentionable, IPresence { + /// Gets the id of this user's avatar. + string AvatarId { get; } /// Gets the url to this user's avatar. string AvatarUrl { get; } /// Gets the per-username unique id for this user. @@ -12,5 +16,10 @@ namespace Discord bool IsBot { get; } /// Gets the username for this user. string Username { get; } + + /// Returns a private message channel to this user, returning null if one does not exist or is not cached. + IDMChannel GetCachedDMChannel(); + /// Returns a private message channel to this user, creating one if it does not already exist. + Task CreateDMChannelAsync(); } } diff --git a/src/Discord.Net/Entities/Users/IVoiceState.cs b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs similarity index 100% rename from src/Discord.Net/Entities/Users/IVoiceState.cs rename to src/Discord.Net.Core/Entities/Users/IVoiceState.cs diff --git a/src/Discord.Net/Entities/Users/StreamType.cs b/src/Discord.Net.Core/Entities/Users/StreamType.cs similarity index 100% rename from src/Discord.Net/Entities/Users/StreamType.cs rename to src/Discord.Net.Core/Entities/Users/StreamType.cs diff --git a/src/Discord.Net/Entities/Users/UserStatus.cs b/src/Discord.Net.Core/Entities/Users/UserStatus.cs similarity index 100% rename from src/Discord.Net/Entities/Users/UserStatus.cs rename to src/Discord.Net.Core/Entities/Users/UserStatus.cs diff --git a/src/Discord.Net/Extensions/DiscordClientExtensions.cs b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs similarity index 100% rename from src/Discord.Net/Extensions/DiscordClientExtensions.cs rename to src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs diff --git a/src/Discord.Net/Extensions/GuildExtensions.cs b/src/Discord.Net.Core/Extensions/GuildExtensions.cs similarity index 100% rename from src/Discord.Net/Extensions/GuildExtensions.cs rename to src/Discord.Net.Core/Extensions/GuildExtensions.cs diff --git a/src/Discord.Net/Extensions/GuildUserExtensions.cs b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs similarity index 75% rename from src/Discord.Net/Extensions/GuildUserExtensions.cs rename to src/Discord.Net.Core/Extensions/GuildUserExtensions.cs index 492b5b76b..57d6a13dc 100644 --- a/src/Discord.Net/Extensions/GuildUserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs @@ -9,11 +9,11 @@ namespace Discord public static Task AddRolesAsync(this IGuildUser user, params IRole[] roles) => AddRolesAsync(user, (IEnumerable)roles); public static Task AddRolesAsync(this IGuildUser user, IEnumerable roles) - => user.ModifyAsync(x => x.Roles = user.Roles.Concat(roles)); + => user.ModifyAsync(x => x.RoleIds = user.RoleIds.Concat(roles.Select(y => y.Id)).ToArray()); public static Task RemoveRolesAsync(this IGuildUser user, params IRole[] roles) => RemoveRolesAsync(user, (IEnumerable)roles); public static Task RemoveRolesAsync(this IGuildUser user, IEnumerable roles) - => user.ModifyAsync(x => x.Roles = user.Roles.Except(roles)); + => user.ModifyAsync(x => x.RoleIds = user.RoleIds.Except(roles.Select(y => y.Id)).ToArray()); } } diff --git a/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs b/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs new file mode 100644 index 000000000..adbf31a7e --- /dev/null +++ b/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs @@ -0,0 +1,10 @@ +using System; + +namespace Discord.Extensions +{ + public static class SnowflakeEntityExtensions + { + //TODO: C#7 Candidate for Extension Property. + public static DateTimeOffset GetCreatedAt(this ISnowflakeEntity entity) => DateTimeUtils.FromSnowflake(entity.Id); + } +} diff --git a/src/Discord.Net/Format.cs b/src/Discord.Net.Core/Format.cs similarity index 100% rename from src/Discord.Net/Format.cs rename to src/Discord.Net.Core/Format.cs diff --git a/src/Discord.Net/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs similarity index 92% rename from src/Discord.Net/IDiscordClient.cs rename to src/Discord.Net.Core/IDiscordClient.cs index 0b3474ed9..accd9080c 100644 --- a/src/Discord.Net/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -1,5 +1,4 @@ using Discord.API; -using Discord.Logging; using System; using System.Collections.Generic; using System.IO; @@ -12,9 +11,8 @@ namespace Discord public interface IDiscordClient : IDisposable { ConnectionState ConnectionState { get; } - DiscordRestApiClient ApiClient { get; } - ILogManager LogManager { get; } + ISelfUser CurrentUser { get; } Task ConnectAsync(); Task DisconnectAsync(); @@ -35,7 +33,6 @@ namespace Discord Task GetUserAsync(ulong id); Task GetUserAsync(string username, string discriminator); - Task GetCurrentUserAsync(); Task> QueryUsersAsync(string query, int limit); Task> GetVoiceRegionsAsync(); diff --git a/src/Discord.Net/Logging/LogMessage.cs b/src/Discord.Net.Core/Logging/LogMessage.cs similarity index 100% rename from src/Discord.Net/Logging/LogMessage.cs rename to src/Discord.Net.Core/Logging/LogMessage.cs diff --git a/src/Discord.Net/LogSeverity.cs b/src/Discord.Net.Core/Logging/LogSeverity.cs similarity index 100% rename from src/Discord.Net/LogSeverity.cs rename to src/Discord.Net.Core/Logging/LogSeverity.cs diff --git a/src/Discord.Net/LoginState.cs b/src/Discord.Net.Core/LoginState.cs similarity index 100% rename from src/Discord.Net/LoginState.cs rename to src/Discord.Net.Core/LoginState.cs diff --git a/src/Discord.Net/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs similarity index 100% rename from src/Discord.Net/Net/Converters/DiscordContractResolver.cs rename to src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs diff --git a/src/Discord.Net/Net/Converters/ImageConverter.cs b/src/Discord.Net.Core/Net/Converters/ImageConverter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/ImageConverter.cs rename to src/Discord.Net.Core/Net/Converters/ImageConverter.cs diff --git a/src/Discord.Net/Net/Converters/NullableUInt64Converter.cs b/src/Discord.Net.Core/Net/Converters/NullableUInt64Converter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/NullableUInt64Converter.cs rename to src/Discord.Net.Core/Net/Converters/NullableUInt64Converter.cs diff --git a/src/Discord.Net/Net/Converters/OptionalConverter.cs b/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/OptionalConverter.cs rename to src/Discord.Net.Core/Net/Converters/OptionalConverter.cs diff --git a/src/Discord.Net/Net/Converters/PermissionTargetConverter.cs b/src/Discord.Net.Core/Net/Converters/PermissionTargetConverter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/PermissionTargetConverter.cs rename to src/Discord.Net.Core/Net/Converters/PermissionTargetConverter.cs diff --git a/src/Discord.Net/Net/Converters/StringEntityConverter.cs b/src/Discord.Net.Core/Net/Converters/StringEntityConverter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/StringEntityConverter.cs rename to src/Discord.Net.Core/Net/Converters/StringEntityConverter.cs diff --git a/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs b/src/Discord.Net.Core/Net/Converters/UInt64ArrayConverter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs rename to src/Discord.Net.Core/Net/Converters/UInt64ArrayConverter.cs diff --git a/src/Discord.Net/Net/Converters/UInt64Converter.cs b/src/Discord.Net.Core/Net/Converters/UInt64Converter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/UInt64Converter.cs rename to src/Discord.Net.Core/Net/Converters/UInt64Converter.cs diff --git a/src/Discord.Net/Net/Converters/UInt64EntityConverter.cs b/src/Discord.Net.Core/Net/Converters/UInt64EntityConverter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/UInt64EntityConverter.cs rename to src/Discord.Net.Core/Net/Converters/UInt64EntityConverter.cs diff --git a/src/Discord.Net/Net/Converters/UserStatusConverter.cs b/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs similarity index 100% rename from src/Discord.Net/Net/Converters/UserStatusConverter.cs rename to src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs diff --git a/src/Discord.Net/Net/HttpException.cs b/src/Discord.Net.Core/Net/HttpException.cs similarity index 100% rename from src/Discord.Net/Net/HttpException.cs rename to src/Discord.Net.Core/Net/HttpException.cs diff --git a/src/Discord.Net/Net/Queue/Definitions/BucketDefinition.cs b/src/Discord.Net.Core/Net/Queue/Definitions/BucketDefinition.cs similarity index 100% rename from src/Discord.Net/Net/Queue/Definitions/BucketDefinition.cs rename to src/Discord.Net.Core/Net/Queue/Definitions/BucketDefinition.cs diff --git a/src/Discord.Net/Net/Queue/Definitions/BucketGroup.cs b/src/Discord.Net.Core/Net/Queue/Definitions/BucketGroup.cs similarity index 100% rename from src/Discord.Net/Net/Queue/Definitions/BucketGroup.cs rename to src/Discord.Net.Core/Net/Queue/Definitions/BucketGroup.cs diff --git a/src/Discord.Net/Net/Queue/Definitions/BucketTarget.cs b/src/Discord.Net.Core/Net/Queue/Definitions/BucketTarget.cs similarity index 100% rename from src/Discord.Net/Net/Queue/Definitions/BucketTarget.cs rename to src/Discord.Net.Core/Net/Queue/Definitions/BucketTarget.cs diff --git a/src/Discord.Net/Net/Queue/Definitions/ChannelBucket.cs b/src/Discord.Net.Core/Net/Queue/Definitions/ChannelBucket.cs similarity index 100% rename from src/Discord.Net/Net/Queue/Definitions/ChannelBucket.cs rename to src/Discord.Net.Core/Net/Queue/Definitions/ChannelBucket.cs diff --git a/src/Discord.Net/Net/Queue/Definitions/GlobalBucket.cs b/src/Discord.Net.Core/Net/Queue/Definitions/GlobalBucket.cs similarity index 100% rename from src/Discord.Net/Net/Queue/Definitions/GlobalBucket.cs rename to src/Discord.Net.Core/Net/Queue/Definitions/GlobalBucket.cs diff --git a/src/Discord.Net/Net/Queue/Definitions/GuildBucket.cs b/src/Discord.Net.Core/Net/Queue/Definitions/GuildBucket.cs similarity index 100% rename from src/Discord.Net/Net/Queue/Definitions/GuildBucket.cs rename to src/Discord.Net.Core/Net/Queue/Definitions/GuildBucket.cs diff --git a/src/Discord.Net/Net/Queue/IQueuedRequest.cs b/src/Discord.Net.Core/Net/Queue/IQueuedRequest.cs similarity index 89% rename from src/Discord.Net/Net/Queue/IQueuedRequest.cs rename to src/Discord.Net.Core/Net/Queue/IQueuedRequest.cs index ad0c8fcb6..cacc58bb7 100644 --- a/src/Discord.Net/Net/Queue/IQueuedRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/IQueuedRequest.cs @@ -6,7 +6,7 @@ namespace Discord.Net.Queue { //TODO: Allow user-supplied canceltoken //TODO: Allow specifying timeout via DiscordApiClient - internal interface IQueuedRequest + public interface IQueuedRequest { CancellationToken CancelToken { get; } int? TimeoutTick { get; } diff --git a/src/Discord.Net/Net/Queue/RequestQueue.cs b/src/Discord.Net.Core/Net/Queue/RequestQueue.cs similarity index 97% rename from src/Discord.Net/Net/Queue/RequestQueue.cs rename to src/Discord.Net.Core/Net/Queue/RequestQueue.cs index 37e5f816c..5300905c8 100644 --- a/src/Discord.Net/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueue.cs @@ -107,13 +107,13 @@ namespace Discord.Net.Queue finally { _lock.Release(); } } - internal async Task SendAsync(RestRequest request, BucketGroup group, int bucketId, ulong objId) + public async Task SendAsync(RestRequest request, BucketGroup group, int bucketId, ulong objId) { request.CancelToken = _cancelToken; var bucket = GetBucket(group, bucketId, objId); return await bucket.SendAsync(request).ConfigureAwait(false); } - internal async Task SendAsync(WebSocketRequest request, BucketGroup group, int bucketId, ulong objId) + public async Task SendAsync(WebSocketRequest request, BucketGroup group, int bucketId, ulong objId) { request.CancelToken = _cancelToken; var bucket = GetBucket(group, bucketId, objId); diff --git a/src/Discord.Net/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs similarity index 100% rename from src/Discord.Net/Net/Queue/RequestQueueBucket.cs rename to src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs diff --git a/src/Discord.Net/Net/Queue/RestRequest.cs b/src/Discord.Net.Core/Net/Queue/RestRequest.cs similarity index 97% rename from src/Discord.Net/Net/Queue/RestRequest.cs rename to src/Discord.Net.Core/Net/Queue/RestRequest.cs index 59a106e96..4be4c746c 100644 --- a/src/Discord.Net/Net/Queue/RestRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/RestRequest.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - internal class RestRequest : IQueuedRequest + public class RestRequest : IQueuedRequest { public IRestClient Client { get; } public string Method { get; } diff --git a/src/Discord.Net/Net/Queue/WebSocketRequest.cs b/src/Discord.Net.Core/Net/Queue/WebSocketRequest.cs similarity index 65% rename from src/Discord.Net/Net/Queue/WebSocketRequest.cs rename to src/Discord.Net.Core/Net/Queue/WebSocketRequest.cs index a7cffbcf9..83f40a4d6 100644 --- a/src/Discord.Net/Net/Queue/WebSocketRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/WebSocketRequest.cs @@ -6,27 +6,22 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - internal class WebSocketRequest : IQueuedRequest + public class WebSocketRequest : IQueuedRequest { public IWebSocketClient Client { get; } public byte[] Data { get; } - public int DataIndex { get; } - public int DataCount { get; } public bool IsText { get; } public int? TimeoutTick { get; } public TaskCompletionSource Promise { get; } public CancellationToken CancelToken { get; set; } - - public WebSocketRequest(IWebSocketClient client, byte[] data, bool isText, RequestOptions options) : this(client, data, 0, data.Length, isText, options) { } - public WebSocketRequest(IWebSocketClient client, byte[] data, int index, int count, bool isText, RequestOptions options) + + public WebSocketRequest(IWebSocketClient client, byte[] data, bool isText, RequestOptions options) { if (options == null) options = RequestOptions.Default; Client = client; Data = data; - DataIndex = index; - DataCount = count; IsText = isText; TimeoutTick = options.Timeout.HasValue ? (int?)unchecked(Environment.TickCount + options.Timeout.Value) : null; Promise = new TaskCompletionSource(); @@ -34,7 +29,7 @@ namespace Discord.Net.Queue public async Task SendAsync() { - await Client.SendAsync(Data, DataIndex, DataCount, IsText).ConfigureAwait(false); + await Client.SendAsync(Data, 0, Data.Length, IsText).ConfigureAwait(false); return null; } } diff --git a/src/Discord.Net/Net/RateLimitException.cs b/src/Discord.Net.Core/Net/RateLimitException.cs similarity index 100% rename from src/Discord.Net/Net/RateLimitException.cs rename to src/Discord.Net.Core/Net/RateLimitException.cs diff --git a/src/Discord.Net/Net/Rest/DefaultRestClient.cs b/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs similarity index 100% rename from src/Discord.Net/Net/Rest/DefaultRestClient.cs rename to src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs diff --git a/src/Discord.Net/Net/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs similarity index 100% rename from src/Discord.Net/Net/Rest/IRestClient.cs rename to src/Discord.Net.Core/Net/Rest/IRestClient.cs diff --git a/src/Discord.Net/Net/Rest/MultipartFile.cs b/src/Discord.Net.Core/Net/Rest/MultipartFile.cs similarity index 100% rename from src/Discord.Net/Net/Rest/MultipartFile.cs rename to src/Discord.Net.Core/Net/Rest/MultipartFile.cs diff --git a/src/Discord.Net/Net/Rest/RestClientProvider.cs b/src/Discord.Net.Core/Net/Rest/RestClientProvider.cs similarity index 100% rename from src/Discord.Net/Net/Rest/RestClientProvider.cs rename to src/Discord.Net.Core/Net/Rest/RestClientProvider.cs diff --git a/src/Discord.Net/Net/RpcException.cs b/src/Discord.Net.Core/Net/RpcException.cs similarity index 100% rename from src/Discord.Net/Net/RpcException.cs rename to src/Discord.Net.Core/Net/RpcException.cs diff --git a/src/Discord.Net/Net/WebSocketException.cs b/src/Discord.Net.Core/Net/WebSocketException.cs similarity index 100% rename from src/Discord.Net/Net/WebSocketException.cs rename to src/Discord.Net.Core/Net/WebSocketException.cs diff --git a/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs similarity index 100% rename from src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs rename to src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs diff --git a/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs b/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs similarity index 100% rename from src/Discord.Net/Net/WebSockets/IWebSocketClient.cs rename to src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs diff --git a/src/Discord.Net/Net/WebSockets/WebSocketProvider.cs b/src/Discord.Net.Core/Net/WebSockets/WebSocketProvider.cs similarity index 100% rename from src/Discord.Net/Net/WebSockets/WebSocketProvider.cs rename to src/Discord.Net.Core/Net/WebSockets/WebSocketProvider.cs diff --git a/src/Discord.Net/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs similarity index 100% rename from src/Discord.Net/RequestOptions.cs rename to src/Discord.Net.Core/RequestOptions.cs diff --git a/src/Discord.Net/TokenType.cs b/src/Discord.Net.Core/TokenType.cs similarity index 100% rename from src/Discord.Net/TokenType.cs rename to src/Discord.Net.Core/TokenType.cs diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs new file mode 100644 index 000000000..acfe35a03 --- /dev/null +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -0,0 +1,85 @@ +using System; +using System.Globalization; + +namespace Discord +{ + public static class MentionUtils + { + //If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake) + public static string MentionUser(ulong id) => MentionsHelper.MentionUser(id, true); + public static string MentionChannel(ulong id) => MentionsHelper.MentionChannel(id); + public static string MentionRole(ulong id) => MentionsHelper.MentionRole(id); + + /// Parses a provided user mention string. + public static ulong ParseUser(string mentionText) + { + ulong id; + if (TryParseUser(mentionText, out id)) + return id; + throw new ArgumentException("Invalid mention format", nameof(mentionText)); + } + /// Tries to parse a provided user mention string. + public static bool TryParseUser(string mentionText, out ulong userId) + { + mentionText = mentionText.Trim(); + if (mentionText.Length >= 3 && mentionText[0] == '<' && mentionText[1] == '@' && mentionText[mentionText.Length - 1] == '>') + { + if (mentionText.Length >= 4 && mentionText[2] == '!') + mentionText = mentionText.Substring(3, mentionText.Length - 4); //<@!123> + else + mentionText = mentionText.Substring(2, mentionText.Length - 3); //<@123> + + if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out userId)) + return true; + } + userId = 0; + return false; + } + + /// Parses a provided channel mention string. + public static ulong ParseChannel(string mentionText) + { + ulong id; + if (TryParseChannel(mentionText, out id)) + return id; + throw new ArgumentException("Invalid mention format", nameof(mentionText)); + } + /// Tries to parse a provided channel mention string. + public static bool TryParseChannel(string mentionText, out ulong channelId) + { + mentionText = mentionText.Trim(); + if (mentionText.Length >= 3 && mentionText[0] == '<' && mentionText[1] == '#' && mentionText[mentionText.Length - 1] == '>') + { + mentionText = mentionText.Substring(2, mentionText.Length - 3); //<#123> + + if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out channelId)) + return true; + } + channelId = 0; + return false; + } + + /// Parses a provided role mention string. + public static ulong ParseRole(string mentionText) + { + ulong id; + if (TryParseRole(mentionText, out id)) + return id; + throw new ArgumentException("Invalid mention format", nameof(mentionText)); + } + /// Tries to parse a provided role mention string. + public static bool TryParseRole(string mentionText, out ulong roleId) + { + mentionText = mentionText.Trim(); + if (mentionText.Length >= 4 && mentionText[0] == '<' && mentionText[1] == '@' && mentionText[2] == '&' && mentionText[mentionText.Length - 1] == '>') + { + mentionText = mentionText.Substring(3, mentionText.Length - 4); //<@&123> + + if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out roleId)) + return true; + } + roleId = 0; + return false; + } + } +} diff --git a/src/Discord.Net/Utilities/Optional.cs b/src/Discord.Net.Core/Utils/Optional.cs similarity index 100% rename from src/Discord.Net/Utilities/Optional.cs rename to src/Discord.Net.Core/Utils/Optional.cs diff --git a/src/Discord.Net.Core/project.json b/src/Discord.Net.Core/project.json new file mode 100644 index 000000000..c7b95dcae --- /dev/null +++ b/src/Discord.Net.Core/project.json @@ -0,0 +1,62 @@ +{ + "version": "1.0.0-beta2-*", + "description": "An unofficial .Net API wrapper for the Discord service.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "licenseUrl": "http://opensource.org/licenses/MIT", + "projectUrl": "https://github.com/RogueException/Discord.Net", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "buildOptions": { + "compile": { + "include": [ "../Discord.Net.Utils/**.cs" ] + } + }, + + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } + } + }, + + "dependencies": { + "Microsoft.Win32.Primitives": "4.0.1", + "Newtonsoft.Json": "8.0.3", + "System.Collections.Concurrent": "4.0.12", + "System.Collections.Immutable": "1.2.0", + "System.Interactive.Async": "3.0.0", + "System.IO.Compression": "4.1.0", + "System.IO.FileSystem": "4.0.1", + "System.Net.Http": "4.1.0", + "System.Net.NameResolution": "4.0.0", + "System.Net.Sockets": "4.1.0", + "System.Net.WebSockets.Client": "4.0.0", + "System.Reflection.Extensions": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", + "System.Runtime.Serialization.Primitives": "4.1.1", + "System.Text.RegularExpressions": "4.1.0" + }, + + "frameworks": { + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } + } +} diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xproj b/src/Discord.Net.Rest/Discord.Net.Rest.xproj new file mode 100644 index 000000000..6a5d3e2b8 --- /dev/null +++ b/src/Discord.Net.Rest/Discord.Net.Rest.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + bfc6dc28-0351-4573-926a-d4124244c04f + Discord.Rest + .\obj + .\bin\ + v4.6.1 + + + 2.0 + + + \ No newline at end of file diff --git a/src/Discord.Net/Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs similarity index 65% rename from src/Discord.Net/Rest/DiscordRestClient.cs rename to src/Discord.Net.Rest/DiscordRestClient.cs index 11cf10747..b1668250d 100644 --- a/src/Discord.Net/Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -1,5 +1,4 @@ using Discord.API.Rest; -using Discord.Logging; using Discord.Net; using Discord.Net.Queue; using System; @@ -10,8 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Runtime.InteropServices; -using Discord.Rpc; -using Discord.WebSocket; +using Discord.Logging; namespace Discord.Rest { @@ -27,15 +25,15 @@ namespace Discord.Rest public event Func LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } private readonly AsyncEvent> _loggedOutEvent = new AsyncEvent>(); - internal readonly ILogger _clientLogger, _restLogger, _queueLogger; + internal readonly Logger _clientLogger, _restLogger, _queueLogger; internal readonly SemaphoreSlim _connectionLock; - internal SelfUser _currentUser; private bool _isFirstLogSub; internal bool _isDisposed; public API.DiscordRestApiClient ApiClient { get; } internal LogManager LogManager { get; } public LoginState LoginState { get; private set; } + public RestSelfUser CurrentUser { get; private set; } /// Creates a new REST-only discord client. public DiscordRestClient() : this(new DiscordRestConfig()) { } @@ -62,7 +60,7 @@ namespace Discord.Rest ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); } private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) - => new API.DiscordRestApiClient(config.RestClientProvider, requestQueue: new RequestQueue()); + => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, requestQueue: new RequestQueue()); /// public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) @@ -70,11 +68,11 @@ namespace Discord.Rest await _connectionLock.WaitAsync().ConfigureAwait(false); try { - await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false); + await LoginInternalAsync(tokenType, token).ConfigureAwait(false); } finally { _connectionLock.Release(); } } - private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) + private async Task LoginInternalAsync(TokenType tokenType, string token) { if (_isFirstLogSub) { @@ -89,10 +87,9 @@ namespace Discord.Rest try { await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); - if (validateToken) - await ValidateTokenAsync(tokenType, token).ConfigureAwait(false); - await OnLoginAsync(tokenType, token).ConfigureAwait(false); + CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); + await OnLoginAsync(tokenType, token).ConfigureAwait(false); LoginState = LoginState.LoggedIn; } catch (Exception) @@ -103,27 +100,8 @@ namespace Discord.Rest await _loggedInEvent.InvokeAsync().ConfigureAwait(false); } - protected virtual async Task ValidateTokenAsync(TokenType tokenType, string token) - { - try - { - var user = await GetCurrentUserAsync().ConfigureAwait(false); - if (user == null) //Is using a cached DiscordClient - user = new SelfUser(this, await ApiClient.GetMyUserAsync().ConfigureAwait(false)); - - if (user.IsBot && tokenType == TokenType.User) - throw new InvalidOperationException($"A bot token used provided with {nameof(TokenType)}.{nameof(TokenType.User)}"); - else if (!user.IsBot && tokenType == TokenType.Bot) //Discord currently sends a 401 in this case - throw new InvalidOperationException($"A user token used provided with {nameof(TokenType)}.{nameof(TokenType.Bot)}"); - } - catch (HttpException ex) - { - throw new ArgumentException("Token validation failed", nameof(token), ex); - } - } protected virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.CompletedTask; - /// public async Task LogoutAsync() { @@ -139,12 +117,10 @@ namespace Discord.Rest if (LoginState == LoginState.LoggedOut) return; LoginState = LoginState.LoggingOut; - await ApiClient.LogoutAsync().ConfigureAwait(false); - + await ApiClient.LogoutAsync().ConfigureAwait(false); await OnLogoutAsync().ConfigureAwait(false); - _currentUser = null; - + CurrentUser = null; LoginState = LoginState.LoggedOut; await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); @@ -155,7 +131,7 @@ namespace Discord.Rest public async Task GetApplicationInfoAsync() { var model = await ApiClient.GetMyApplicationAsync().ConfigureAwait(false); - return new Application(this, model); + return RestApplication.Create(this, model); } /// @@ -164,25 +140,19 @@ namespace Discord.Rest var model = await ApiClient.GetChannelAsync(id).ConfigureAwait(false); if (model != null) { - if (model.GuildId.IsSpecified) - { - var guildModel = await ApiClient.GetGuildAsync(model.GuildId.Value).ConfigureAwait(false); - if (guildModel != null) - { - var guild = new Guild(this, guildModel); - return guild.ToChannel(model); - } - } - else if (model.Type == ChannelType.DM) - return new DMChannel(this, new User(model.Recipients.Value[0]), model); - else if (model.Type == ChannelType.Group) + switch (model.Type) { - var channel = new GroupChannel(this, model); - channel.UpdateUsers(model.Recipients.Value, UpdateSource.Creation); - return channel; + case ChannelType.Text: + return RestTextChannel.Create(this, model); + case ChannelType.Voice: + return RestVoiceChannel.Create(this, model); + case ChannelType.DM: + return RestDMChannel.Create(this, model); + case ChannelType.Group: + return RestGroupChannel.Create(this, model); + default: + throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); } - else - throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); } return null; } @@ -190,115 +160,103 @@ namespace Discord.Rest public virtual async Task> GetPrivateChannelsAsync() { var models = await ApiClient.GetMyPrivateChannelsAsync().ConfigureAwait(false); - return models.Select(x => new DMChannel(this, new User(x.Recipients.Value[0]), x)).ToImmutableArray(); + return models.Select(x => RestDMChannel.Create(this, x)).ToImmutableArray(); } /// - public async Task> GetConnectionsAsync() + public async Task> GetConnectionsAsync() { var models = await ApiClient.GetMyConnectionsAsync().ConfigureAwait(false); - return models.Select(x => new Connection(x)).ToImmutableArray(); + return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); } /// - public virtual async Task GetInviteAsync(string inviteId) + public virtual async Task GetInviteAsync(string inviteId) { var model = await ApiClient.GetInviteAsync(inviteId).ConfigureAwait(false); if (model != null) - return new Invite(this, model); + return RestInvite.Create(this, model); return null; } /// - public virtual async Task GetGuildAsync(ulong id) + public virtual async Task GetGuildAsync(ulong id) { var model = await ApiClient.GetGuildAsync(id).ConfigureAwait(false); if (model != null) - return new Guild(this, model); + return RestGuild.Create(this, model); return null; } /// - public virtual async Task GetGuildEmbedAsync(ulong id) + public virtual async Task GetGuildEmbedAsync(ulong id) { var model = await ApiClient.GetGuildEmbedAsync(id).ConfigureAwait(false); if (model != null) - return new GuildEmbed(model); + return RestGuildEmbed.Create(model); return null; } /// - public virtual async Task> GetGuildSummariesAsync() + public virtual async Task> GetGuildSummariesAsync() { var models = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false); - return models.Select(x => new UserGuild(this, x)).ToImmutableArray(); + return models.Select(x => RestUserGuild.Create(this, x)).ToImmutableArray(); } /// - public virtual async Task> GetGuildsAsync() + public virtual async Task> GetGuildsAsync() { var summaryModels = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false); - var guilds = ImmutableArray.CreateBuilder(summaryModels.Count); + var guilds = ImmutableArray.CreateBuilder(summaryModels.Count); foreach (var summaryModel in summaryModels) { var guildModel = await ApiClient.GetGuildAsync(summaryModel.Id).ConfigureAwait(false); if (guildModel != null) - guilds.Add(new Guild(this, guildModel)); + guilds.Add(RestGuild.Create(this, guildModel)); } return guilds.ToImmutable(); } /// - public virtual async Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) + public virtual async Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) { - var args = new CreateGuildParams(); + var args = new CreateGuildParams(name, region.Id); var model = await ApiClient.CreateGuildAsync(args).ConfigureAwait(false); - return new Guild(this, model); + return RestGuild.Create(this, model); } /// - public virtual async Task GetUserAsync(ulong id) + public virtual async Task GetUserAsync(ulong id) { var model = await ApiClient.GetUserAsync(id).ConfigureAwait(false); if (model != null) - return new User(model); + return RestUser.Create(this, model); return null; } /// - public virtual async Task GetUserAsync(string username, string discriminator) + public virtual async Task GetUserAsync(string username, string discriminator) { var model = await ApiClient.GetUserAsync(username, discriminator).ConfigureAwait(false); if (model != null) - return new User(model); + return RestUser.Create(this, model); return null; } - /// - public virtual async Task GetCurrentUserAsync() - { - var user = _currentUser; - if (user == null) - { - var model = await ApiClient.GetMyUserAsync().ConfigureAwait(false); - user = new SelfUser(this, model); - _currentUser = user; - } - return user; - } /// - public virtual async Task> QueryUsersAsync(string query, int limit) + public virtual async Task> QueryUsersAsync(string query, int limit) { var models = await ApiClient.QueryUsersAsync(query, limit).ConfigureAwait(false); - return models.Select(x => new User(x)).ToImmutableArray(); + return models.Select(x => RestUser.Create(this, x)).ToImmutableArray(); } /// - public virtual async Task> GetVoiceRegionsAsync() + public virtual async Task> GetVoiceRegionsAsync() { var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); - return models.Select(x => new VoiceRegion(x)).ToImmutableArray(); + return models.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableArray(); } /// - public virtual async Task GetVoiceRegionAsync(string id) + public virtual async Task GetVoiceRegionAsync(string id) { var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); - return models.Select(x => new VoiceRegion(x)).Where(x => x.Id == id).FirstOrDefault(); + return models.Select(x => RestVoiceRegion.Create(this, x)).Where(x => x.Id == id).FirstOrDefault(); } internal virtual void Dispose(bool disposing) @@ -314,17 +272,15 @@ namespace Discord.Rest private async Task WriteInitialLog() { - if (this is DiscordSocketClient) + /*if (this is DiscordSocketClient) await _clientLogger.InfoAsync($"DiscordSocketClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, {DiscordSocketConfig.GatewayEncoding})").ConfigureAwait(false); else if (this is DiscordRpcClient) - await _clientLogger.InfoAsync($"DiscordRpcClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, RPC API v{DiscordRpcConfig.RpcAPIVersion})").ConfigureAwait(false); - else - await _clientLogger.InfoAsync($"DiscordClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); + await _clientLogger.InfoAsync($"DiscordRpcClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, RPC API v{DiscordRpcConfig.RpcAPIVersion})").ConfigureAwait(false);*/ + await _clientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); await _clientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false); await _clientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false); await _clientLogger.VerboseAsync($"Processors: {Environment.ProcessorCount}").ConfigureAwait(false); } - private static string ToArchString(Architecture arch) { switch (arch) @@ -335,8 +291,32 @@ namespace Discord.Rest } } + //IDiscordClient ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; - ILogManager IDiscordClient.LogManager => LogManager; + ISelfUser IDiscordClient.CurrentUser => CurrentUser; + + async Task> IDiscordClient.GetConnectionsAsync() + => await GetConnectionsAsync().ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId) + => await GetInviteAsync(inviteId).ConfigureAwait(false); + async Task IDiscordClient.GetGuildAsync(ulong id) + => await GetGuildAsync(id).ConfigureAwait(false); + async Task> IDiscordClient.GetGuildSummariesAsync() + => await GetGuildSummariesAsync().ConfigureAwait(false); + async Task> IDiscordClient.GetGuildsAsync() + => await GetGuildsAsync().ConfigureAwait(false); + async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) + => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); + async Task IDiscordClient.GetUserAsync(ulong id) + => await GetUserAsync(id).ConfigureAwait(false); + async Task IDiscordClient.GetUserAsync(string username, string discriminator) + => await GetUserAsync(username, discriminator).ConfigureAwait(false); + async Task> IDiscordClient.QueryUsersAsync(string query, int limit) + => await QueryUsersAsync(query, limit).ConfigureAwait(false); + async Task> IDiscordClient.GetVoiceRegionsAsync() + => await GetVoiceRegionsAsync().ConfigureAwait(false); + async Task IDiscordClient.GetVoiceRegionAsync(string id) + => await GetVoiceRegionAsync(id).ConfigureAwait(false); Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); } Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); } diff --git a/src/Discord.Net/Rest/DiscordRestConfig.cs b/src/Discord.Net.Rest/DiscordRestConfig.cs similarity index 100% rename from src/Discord.Net/Rest/DiscordRestConfig.cs rename to src/Discord.Net.Rest/DiscordRestConfig.cs diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs new file mode 100644 index 000000000..26512acaf --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -0,0 +1,211 @@ +using Discord.API.Rest; +using System.IO; +using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Collections.Immutable; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + internal static class ChannelHelper + { + //General + public static async Task GetAsync(IGuildChannel channel, DiscordRestClient client) + { + return await client.ApiClient.GetChannelAsync(channel.GuildId, channel.Id).ConfigureAwait(false); + } + public static async Task GetAsync(IPrivateChannel channel, DiscordRestClient client) + { + return await client.ApiClient.GetChannelAsync(channel.Id).ConfigureAwait(false); + } + public static async Task DeleteAsync(IChannel channel, DiscordRestClient client) + { + await client.ApiClient.DeleteChannelAsync(channel.Id).ConfigureAwait(false); + } + public static async Task ModifyAsync(IGuildChannel channel, DiscordRestClient client, + Action func) + { + var args = new ModifyGuildChannelParams(); + func(args); + await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); + } + public static async Task ModifyAsync(ITextChannel channel, DiscordRestClient client, + Action func) + { + var args = new ModifyTextChannelParams(); + func(args); + await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); + } + public static async Task ModifyAsync(IVoiceChannel channel, DiscordRestClient client, + Action func) + { + var args = new ModifyVoiceChannelParams(); + func(args); + await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); + } + + //Invites + public static async Task> GetInvitesAsync(IChannel channel, DiscordRestClient client) + { + var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id); + return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); + } + public static async Task CreateInviteAsync(IChannel channel, DiscordRestClient client, + int? maxAge, int? maxUses, bool isTemporary) + { + var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; + if (maxAge.HasValue) + args.MaxAge = maxAge.Value; + if (maxUses.HasValue) + args.MaxUses = maxUses.Value; + var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args); + return RestInviteMetadata.Create(client, model); + } + + //Messages + public static async Task GetMessageAsync(IChannel channel, DiscordRestClient client, + ulong id) + { + var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false); + return RestMessage.Create(client, model); + } + public static PagedAsyncEnumerable GetMessagesAsync(IChannel channel, DiscordRestClient client, + ulong? fromMessageId = null, Direction dir = Direction.Before, int limit = DiscordConfig.MaxMessagesPerBatch) + { + //TODO: Test this with Around direction + return new PagedAsyncEnumerable( + DiscordConfig.MaxMessagesPerBatch, + async (info, ct) => + { + var args = new GetChannelMessagesParams + { + RelativeDirection = dir, + Limit = info.PageSize + }; + if (info.Position != null) + args.RelativeMessageId = info.Position.Value; + var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args); + return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); ; + }, + nextPage: (info, lastPage) => + { + if (dir == Direction.Before) + info.Position = lastPage.Min(x => x.Id); + else + info.Position = lastPage.Max(x => x.Id); + if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) + info.Remaining = 0; + }, + start: fromMessageId, + count: (uint)limit + ); + } + public static async Task> GetPinnedMessagesAsync(IChannel channel, DiscordRestClient client) + { + var models = await client.ApiClient.GetPinsAsync(channel.Id).ConfigureAwait(false); + return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); + } + + public static async Task SendMessageAsync(IChannel channel, DiscordRestClient client, + string text, bool isTTS) + { + var args = new CreateMessageParams(text) { IsTTS = isTTS }; + var model = await client.ApiClient.CreateMessageAsync(channel.Id, args).ConfigureAwait(false); + return RestUserMessage.Create(client, model); + } + + public static Task SendFileAsync(IChannel channel, DiscordRestClient client, + string filePath, string text, bool isTTS) + { + string filename = Path.GetFileName(filePath); + using (var file = File.OpenRead(filePath)) + return SendFileAsync(channel, client, file, filename, text, isTTS); + } + public static async Task SendFileAsync(IChannel channel, DiscordRestClient client, + Stream stream, string filename, string text, bool isTTS) + { + var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; + var model = await client.ApiClient.UploadFileAsync(channel.Id, args).ConfigureAwait(false); + return RestUserMessage.Create(client, model); + } + + public static async Task DeleteMessagesAsync(IChannel channel, DiscordRestClient client, + IEnumerable messages) + { + var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); + await client.ApiClient.DeleteMessagesAsync(channel.Id, args).ConfigureAwait(false); + } + + //Permission Overwrites + public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, + IUser user, OverwritePermissions perms) + { + var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); + await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args).ConfigureAwait(false); + } + public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, + IRole role, OverwritePermissions perms) + { + var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); + await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args).ConfigureAwait(false); + } + public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, + IUser user) + { + await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id).ConfigureAwait(false); + } + public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, + IRole role) + { + await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false); + } + + //Users + public static async Task GetUserAsync(IGuildChannel channel, DiscordRestClient client, + ulong id) + { + var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id); + if (model == null) + return null; + var user = RestGuildUser.Create(client, model); + if (!user.GetPermissions(channel).ReadMessages) + return null; + + return user; + } + public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, DiscordRestClient client, + ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch) + { + 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(channel.GuildId, args); + return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); ; + }, + nextPage: (info, lastPage) => + { + info.Position = lastPage.Max(x => x.Id); + if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) + info.Remaining = 0; + }, + start: froUserId, + count: limit + ); + } + + //Typing + public static IDisposable EnterTypingState(IChannel channel, DiscordRestClient client) + { + throw new NotImplementedException(); //TODO: Impl + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs new file mode 100644 index 000000000..796ec23cb --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestDMChannel : RestEntity, IDMChannel, IUpdateable + { + public RestUser Recipient { get; } + + public IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); + + internal RestDMChannel(DiscordRestClient discord, ulong id) + : base(discord, id) + { + } + internal static RestDMChannel Create(DiscordRestClient discord, Model model) + { + var entity = new RestDMChannel(discord, model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Recipient.Update(model.Recipients.Value[0]); + } + + public async Task UpdateAsync() + => Update(await ChannelHelper.GetAsync(this, Discord)); + public Task CloseAsync() + => ChannelHelper.DeleteAsync(this, Discord); + + public RestUser GetUser(ulong id) + { + if (id == Recipient.Id) + return Recipient; + else if (id == Discord.CurrentUser.Id) + return Discord.CurrentUser; + else + return null; + } + + public Task GetMessageAsync(ulong id) + => ChannelHelper.GetMessageAsync(this, Discord, id); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + public Task> GetPinnedMessagesAsync() + => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + + public Task SendMessageAsync(string text, bool isTTS) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); + public Task SendFileAsync(string filePath, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + + public Task DeleteMessagesAsync(IEnumerable messages) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + + public IDisposable EnterTypingState() + => ChannelHelper.EnterTypingState(this, Discord); + + public override string ToString() => $"@{Recipient}"; + private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; + + //IDMChannel + IUser IDMChannel.Recipient => Recipient; + + //IPrivateChannel + IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + + //IMessageChannel + IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); + IMessage IMessageChannel.GetCachedMessage(ulong id) => null; + + async Task IMessageChannel.GetMessageAsync(ulong id) + => await GetMessageAsync(id); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) + => GetMessagesAsync(limit); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) + => GetMessagesAsync(fromMessageId, dir, limit); + async Task> IMessageChannel.GetPinnedMessagesAsync() + => await GetPinnedMessagesAsync().ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) + => await SendFileAsync(filePath, text, isTTS); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => await SendFileAsync(stream, filename, text, isTTS); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) + => await SendMessageAsync(text, isTTS); + IDisposable IMessageChannel.EnterTypingState() + => EnterTypingState(); + + //IChannel + IReadOnlyCollection IChannel.CachedUsers => Users; + + IUser IChannel.GetCachedUser(ulong id) + => GetUser(id); + Task IChannel.GetUserAsync(ulong id) + => Task.FromResult(GetUser(id)); + IAsyncEnumerable> IChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs new file mode 100644 index 000000000..b95cfce14 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestGroupChannel : RestEntity, IGroupChannel, 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(DiscordRestClient discord, ulong id) + : base(discord, id) + { + } + internal static RestGroupChannel Create(DiscordRestClient discord, Model model) + { + var entity = new RestGroupChannel(discord, model.Id); + entity.Update(model); + return entity; + } + internal 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 virtual 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 async Task UpdateAsync() + => Update(await ChannelHelper.GetAsync(this, Discord)); + public Task LeaveAsync() + => ChannelHelper.DeleteAsync(this, Discord); + + public IUser GetUser(ulong id) + { + RestGroupUser user; + if (_users.TryGetValue(id, out user)) + return user; + return null; + } + + public Task GetMessageAsync(ulong id) + => ChannelHelper.GetMessageAsync(this, Discord, id); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + public Task> GetPinnedMessagesAsync() + => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + + public Task SendMessageAsync(string text, bool isTTS) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); + public Task SendFileAsync(string filePath, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + + public Task DeleteMessagesAsync(IEnumerable messages) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + + public IDisposable EnterTypingState() + => ChannelHelper.EnterTypingState(this, Discord); + + //IPrivateChannel + IReadOnlyCollection IPrivateChannel.Recipients => Recipients; + + //IMessageChannel + IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); + + IMessage IMessageChannel.GetCachedMessage(ulong id) + => null; + async Task IMessageChannel.GetMessageAsync(ulong id) + => await GetMessageAsync(id); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) + => GetMessagesAsync(limit); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) + => GetMessagesAsync(fromMessageId, dir, limit); + async Task> IMessageChannel.GetPinnedMessagesAsync() + => await GetPinnedMessagesAsync(); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) + => await SendFileAsync(filePath, text, isTTS); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => await SendFileAsync(stream, filename, text, isTTS); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) + => await SendMessageAsync(text, isTTS); + IDisposable IMessageChannel.EnterTypingState() + => EnterTypingState(); + + //IChannel + IReadOnlyCollection IChannel.CachedUsers => Users; + + IUser IChannel.GetCachedUser(ulong id) + => GetUser(id); + Task IChannel.GetUserAsync(ulong id) + => Task.FromResult(GetUser(id)); + IAsyncEnumerable> IChannel.GetUsersAsync() + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs new file mode 100644 index 000000000..62422efe1 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -0,0 +1,158 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public abstract class RestGuildChannel : RestEntity, IGuildChannel, IUpdateable + { + private ImmutableArray _overwrites; + + public IReadOnlyCollection PermissionOverwrites => _overwrites; + + public ulong GuildId { get; } + + public string Name { get; private set; } + public int Position { get; private set; } + + internal RestGuildChannel(DiscordRestClient discord, ulong id, ulong guildId) + : base(discord, id) + { + GuildId = guildId; + } + internal static RestGuildChannel Create(DiscordRestClient discord, Model model) + { + switch (model.Type) + { + case ChannelType.Text: + return RestTextChannel.Create(discord, model); + case ChannelType.Voice: + return RestVoiceChannel.Create(discord, model); + default: + throw new InvalidOperationException("Unknown guild channel type"); + } + } + internal virtual void Update(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(new Overwrite(overwrites[i])); + _overwrites = newOverwrites.ToImmutable(); + } + + public async Task UpdateAsync() + => Update(await ChannelHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) + => ChannelHelper.ModifyAsync(this, Discord, func); + public Task DeleteAsync() + => ChannelHelper.DeleteAsync(this, Discord); + + public OverwritePermissions? GetPermissionOverwrite(IUser user) + { + for (int 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++) + { + if (_overwrites[i].TargetId == role.Id) + return _overwrites[i].Permissions; + } + return null; + } + public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms) + { + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms).ConfigureAwait(false); + _overwrites = _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User })); + } + public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms) + { + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms).ConfigureAwait(false); + _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role })); + } + public async Task RemovePermissionOverwriteAsync(IUser user) + { + await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user).ConfigureAwait(false); + + for (int i = 0; i < _overwrites.Length; i++) + { + if (_overwrites[i].TargetId == user.Id) + { + _overwrites = _overwrites.RemoveAt(i); + return; + } + } + } + public async Task RemovePermissionOverwriteAsync(IRole role) + { + await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role).ConfigureAwait(false); + + for (int i = 0; i < _overwrites.Length; i++) + { + if (_overwrites[i].TargetId == role.Id) + { + _overwrites = _overwrites.RemoveAt(i); + return; + } + } + } + + public async Task> GetInvitesAsync() + => await ChannelHelper.GetInvitesAsync(this, Discord); + public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); + + //IGuildChannel + async Task> IGuildChannel.GetInvitesAsync() + => await GetInvitesAsync(); + async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) + => await CreateInviteAsync(maxAge, maxUses, isTemporary); + + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) + => GetPermissionOverwrite(role); + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) + => GetPermissionOverwrite(user); + async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions) + => await AddPermissionOverwriteAsync(role, permissions); + async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions) + => await AddPermissionOverwriteAsync(user, permissions); + async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role) + => await RemovePermissionOverwriteAsync(role); + async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) + => await RemovePermissionOverwriteAsync(user); + + IReadOnlyCollection IGuildChannel.CachedUsers + => ImmutableArray.Create(); + IAsyncEnumerable> IGuildChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? + Task IGuildChannel.GetUserAsync(ulong id) + => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? + IGuildUser IGuildChannel.GetCachedUser(ulong id) + => null; + + //IChannel + IReadOnlyCollection IChannel.CachedUsers + => ImmutableArray.Create(); + IUser IChannel.GetCachedUser(ulong id) + => null; + IAsyncEnumerable> IChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? + Task IChannel.GetUserAsync(ulong id) + => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs new file mode 100644 index 000000000..65877868c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -0,0 +1,98 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestTextChannel : RestGuildChannel, ITextChannel + { + public string Topic { get; private set; } + + public string Mention => MentionUtils.MentionChannel(Id); + + internal RestTextChannel(DiscordRestClient discord, ulong id, ulong guildId) + : base(discord, id, guildId) + { + } + internal new static RestTextChannel Create(DiscordRestClient discord, Model model) + { + var entity = new RestTextChannel(discord, model.Id, model.GuildId.Value); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + + Topic = model.Topic.Value; + } + + + public Task ModifyAsync(Action func) + => ChannelHelper.ModifyAsync(this, Discord, func); + + public Task GetUserAsync(ulong id) + => ChannelHelper.GetUserAsync(this, Discord, id); + public IAsyncEnumerable> GetUsersAsync() + => ChannelHelper.GetUsersAsync(this, Discord); + + public Task GetMessageAsync(ulong id) + => ChannelHelper.GetMessageAsync(this, Discord, id); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + public Task> GetPinnedMessagesAsync() + => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + + public Task SendMessageAsync(string text, bool isTTS) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); + public Task SendFileAsync(string filePath, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + + public Task DeleteMessagesAsync(IEnumerable messages) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + + public IDisposable EnterTypingState() + => ChannelHelper.EnterTypingState(this, Discord); + + //IGuildChannel + async Task IGuildChannel.GetUserAsync(ulong id) + => await GetUserAsync(id); + IAsyncEnumerable> IGuildChannel.GetUsersAsync() + => GetUsersAsync(); + + //IMessageChannel + IReadOnlyCollection IMessageChannel.CachedMessages + => ImmutableArray.Create(); + IMessage IMessageChannel.GetCachedMessage(ulong id) + => null; + async Task IMessageChannel.GetMessageAsync(ulong id) + => await GetMessageAsync(id); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) + => GetMessagesAsync(limit); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) + => GetMessagesAsync(fromMessageId, dir, limit); + async Task> IMessageChannel.GetPinnedMessagesAsync() + => await GetPinnedMessagesAsync(); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) + => await SendFileAsync(filePath, text, isTTS); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => await SendFileAsync(stream, filename, text, isTTS); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) + => await SendMessageAsync(text, isTTS); + IDisposable IMessageChannel.EnterTypingState() + => EnterTypingState(); + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs new file mode 100644 index 000000000..a3187ff12 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -0,0 +1,49 @@ +using Discord.API.Rest; +using Discord.Audio; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestVoiceChannel : RestGuildChannel, IVoiceChannel + { + public int Bitrate { get; private set; } + public int UserLimit { get; private set; } + + internal RestVoiceChannel(DiscordRestClient discord, ulong id, ulong guildId) + : base(discord, id, guildId) + { + } + internal new static RestVoiceChannel Create(DiscordRestClient discord, Model model) + { + var entity = new RestVoiceChannel(discord, model.Id, model.GuildId.Value); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + + Bitrate = model.Bitrate.Value; + UserLimit = model.UserLimit.Value; + } + + public Task ModifyAsync(Action func) + => ChannelHelper.ModifyAsync(this, Discord, func); + + //IVoiceChannel + Task IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } + + //IGuildChannel + Task IGuildChannel.GetUserAsync(ulong id) + => Task.FromResult(null); + IAsyncEnumerable> IGuildChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); + } +} diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs new file mode 100644 index 000000000..02fb76868 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -0,0 +1,183 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Model = Discord.API.Guild; +using EmbedModel = Discord.API.GuildEmbed; +using RoleModel = Discord.API.Role; +using System.Linq; +using System.Collections.Immutable; + +namespace Discord.Rest +{ + internal static class GuildHelper + { + //General + public static async Task ModifyAsync(IGuild guild, DiscordRestClient client, + Action func) + { + if (func == null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyGuildParams(); + func(args); + + if (args.Splash.IsSpecified && guild.SplashId != null) + args.Splash = new API.Image(guild.SplashId); + if (args.Icon.IsSpecified && guild.IconId != null) + args.Icon = new API.Image(guild.IconId); + + return await client.ApiClient.ModifyGuildAsync(guild.Id, args).ConfigureAwait(false); + } + public static async Task ModifyEmbedAsync(IGuild guild, DiscordRestClient client, + Action func) + { + if (func == null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyGuildEmbedParams(); + func(args); + return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args).ConfigureAwait(false); + } + public static async Task ModifyChannelsAsync(IGuild guild, DiscordRestClient client, + IEnumerable args) + { + await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args).ConfigureAwait(false); + } + public static async Task> ModifyRolesAsync(IGuild guild, DiscordRestClient client, + IEnumerable args) + { + return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args).ConfigureAwait(false); + } + public static async Task LeaveAsync(IGuild guild, DiscordRestClient client) + { + await client.ApiClient.LeaveGuildAsync(guild.Id).ConfigureAwait(false); + } + public static async Task DeleteAsync(IGuild guild, DiscordRestClient client) + { + await client.ApiClient.DeleteGuildAsync(guild.Id).ConfigureAwait(false); + } + + //Bans + public static async Task> GetBansAsync(IGuild guild, DiscordRestClient client) + { + var models = await client.ApiClient.GetGuildBansAsync(guild.Id); + return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); + } + + public static async Task AddBanAsync(IGuild guild, DiscordRestClient client, + ulong userId, int pruneDays) + { + var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; + await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args); + } + public static async Task RemoveBanAsync(IGuild guild, DiscordRestClient client, + ulong userId) + { + await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId); + } + + //Channels + public static async Task GetChannelAsync(IGuild guild, DiscordRestClient client, + ulong id) + { + var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false); + if (model != null) + return RestGuildChannel.Create(client, model); + return null; + } + public static async Task> GetChannelsAsync(IGuild guild, DiscordRestClient client) + { + var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); + return models.Select(x => RestGuildChannel.Create(client, x)).ToImmutableArray(); + } + public static async Task CreateTextChannelAsync(IGuild guild, DiscordRestClient client, + string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + var args = new CreateGuildChannelParams(name, ChannelType.Text); + var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); + return RestTextChannel.Create(client, model); + } + public static async Task CreateVoiceChannelAsync(IGuild guild, DiscordRestClient client, + string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + var args = new CreateGuildChannelParams(name, ChannelType.Voice); + var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); + return RestVoiceChannel.Create(client, model); + } + + //Integrations + public static async Task> GetIntegrationsAsync(IGuild guild, DiscordRestClient client) + { + var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id).ConfigureAwait(false); + return models.Select(x => RestGuildIntegration.Create(client, x)).ToImmutableArray(); + } + public static async Task CreateIntegrationAsync(IGuild guild, DiscordRestClient client, + ulong id, string type) + { + var args = new CreateGuildIntegrationParams(id, type); + var model = await client.ApiClient.CreateGuildIntegrationAsync(guild.Id, args).ConfigureAwait(false); + return RestGuildIntegration.Create(client, model); + } + + //Invites + public static async Task> GetInvitesAsync(IGuild guild, DiscordRestClient client) + { + var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id).ConfigureAwait(false); + return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); + } + + //Roles + public static async Task CreateRoleAsync(IGuild guild, DiscordRestClient client, + string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id).ConfigureAwait(false); + var role = RestRole.Create(client, model); + + await role.ModifyAsync(x => + { + x.Name = name; + x.Permissions = (permissions ?? role.Permissions).RawValue; + x.Color = (color ?? Color.Default).RawValue; + x.Hoist = isHoisted; + }).ConfigureAwait(false); + + return role; + } + + //Users + public static async Task GetUserAsync(IGuild guild, DiscordRestClient client, + ulong id) + { + var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false); + if (model != null) + return RestGuildUser.Create(client, model); + return null; + } + public static async Task GetCurrentUserAsync(IGuild guild, DiscordRestClient client) + { + return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false); + } + public static async Task> GetUsersAsync(IGuild guild, DiscordRestClient client) + { + var args = new GetGuildMembersParams(); + var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false); + return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); + } + public static async Task PruneUsersAsync(IGuild guild, DiscordRestClient client, + int days = 30, bool simulate = false) + { + var args = new GuildPruneParams(days); + GetGuildPruneCountResponse model; + if (simulate) + model = await client.ApiClient.GetGuildPruneCountAsync(guild.Id, args).ConfigureAwait(false); + else + model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args).ConfigureAwait(false); + return model.Pruned; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs new file mode 100644 index 000000000..8e2b65576 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using Model = Discord.API.Ban; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{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(DiscordRestClient client, Model model) + { + return new RestBan(RestUser.Create(client, model.User), model.Reason); + } + + public override string ToString() => User.ToString(); + private string DebuggerDisplay => $"{User}: {Reason}"; + + IUser IBan.User => User; + } +} diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs new file mode 100644 index 000000000..4735debf9 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -0,0 +1,212 @@ +using System.Diagnostics; +using System.Collections.Immutable; +using Discord.Audio; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Discord.API.Rest; +using Model = Discord.API.Guild; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestGuild : RestEntity, IGuild, IUpdateable + { + private ImmutableDictionary _roles; + private ImmutableArray _emojis; + private ImmutableArray _features; + + 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? AFKChannelId { get; private set; } + public ulong? EmbedChannelId { 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 ulong DefaultChannelId => Id; + public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); + public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId); + + public RestRole EveryoneRole => GetRole(Id); + public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + public IReadOnlyCollection Emojis => _emojis; + public IReadOnlyCollection Features => _features; + + internal RestGuild(DiscordRestClient client, ulong id) + : base(client, id) + { + } + internal static RestGuild Create(DiscordRestClient discord, Model model) + { + var entity = new RestGuild(discord, model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + AFKChannelId = model.AFKChannelId; + EmbedChannelId = model.EmbedChannelId; + AFKTimeout = model.AFKTimeout; + IsEmbeddable = model.EmbedEnabled; + IconId = model.Icon; + Name = model.Name; + OwnerId = model.OwnerId; + VoiceRegionId = model.Region; + SplashId = model.Splash; + VerificationLevel = model.VerificationLevel; + MfaLevel = model.MfaLevel; + DefaultMessageNotifications = model.DefaultMessageNotifications; + + if (model.Emojis != null) + { + var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); + for (int i = 0; i < model.Emojis.Length; i++) + emojis.Add(Emoji.Create(model.Emojis[i])); + _emojis = emojis.ToImmutableArray(); + } + else + _emojis = ImmutableArray.Create(); + + if (model.Features != null) + _features = model.Features.ToImmutableArray(); + else + _features = 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, model.Roles[i]); + } + _roles = roles.ToImmutable(); + } + + //General + public async Task UpdateAsync() + => Update(await Discord.ApiClient.GetGuildAsync(Id)); + public Task DeleteAsync() + => GuildHelper.DeleteAsync(this, Discord); + + public Task ModifyAsync(Action func) + => GuildHelper.ModifyAsync(this, Discord, func); + public Task ModifyEmbedAsync(Action func) + => GuildHelper.ModifyEmbedAsync(this, Discord, func); + public Task ModifyChannelsAsync(IEnumerable args) + => GuildHelper.ModifyChannelsAsync(this, Discord, args); + public Task ModifyRolesAsync(IEnumerable args) + => GuildHelper.ModifyRolesAsync(this, Discord, args); + + public Task LeaveAsync() + => GuildHelper.LeaveAsync(this, Discord); + + //Bans + public Task> GetBansAsync() + => GuildHelper.GetBansAsync(this, Discord); + + public Task AddBanAsync(IUser user, int pruneDays = 0) + => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays); + public Task AddBanAsync(ulong userId, int pruneDays = 0) + => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays); + + public Task RemoveBanAsync(IUser user) + => GuildHelper.RemoveBanAsync(this, Discord, user.Id); + public Task RemoveBanAsync(ulong userId) + => GuildHelper.RemoveBanAsync(this, Discord, userId); + + //Channels + public Task> GetChannelsAsync() + => GuildHelper.GetChannelsAsync(this, Discord); + public Task GetChannelAsync(ulong id) + => GuildHelper.GetChannelAsync(this, Discord, id); + public Task CreateTextChannelAsync(string name) + => GuildHelper.CreateTextChannelAsync(this, Discord, name); + public Task CreateVoiceChannelAsync(string name) + => GuildHelper.CreateVoiceChannelAsync(this, Discord, name); + + //Integrations + public Task> GetIntegrationsAsync() + => GuildHelper.GetIntegrationsAsync(this, Discord); + public Task CreateIntegrationAsync(ulong id, string type) + => GuildHelper.CreateIntegrationAsync(this, Discord, id, type); + + //Invites + public Task> GetInvitesAsync() + => GuildHelper.GetInvitesAsync(this, Discord); + + //Roles + public RestRole GetRole(ulong id) + { + RestRole value; + if (_roles.TryGetValue(id, out value)) + return value; + return null; + } + + public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) + { + var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); + _roles = _roles.Add(role.Id, role); + return role; + } + + //Users + public Task> GetUsersAsync() + => GuildHelper.GetUsersAsync(this, Discord); + public Task GetUserAsync(ulong id) + => GuildHelper.GetUserAsync(this, Discord, id); + public Task GetCurrentUserAsync() + => GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id); + + public Task PruneUsersAsync(int days = 30, bool simulate = false) + => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); + + //IGuild + bool IGuild.Available => true; + IAudioClient IGuild.AudioClient => null; + IReadOnlyCollection IGuild.CachedUsers => ImmutableArray.Create(); + IRole IGuild.EveryoneRole => EveryoneRole; + IReadOnlyCollection IGuild.Roles => Roles; + + async Task> IGuild.GetBansAsync() + => await GetBansAsync(); + + async Task> IGuild.GetChannelsAsync() + => await GetChannelsAsync(); + async Task IGuild.GetChannelAsync(ulong id) + => await GetChannelAsync(id); + IGuildChannel IGuild.GetCachedChannel(ulong id) + => null; + async Task IGuild.CreateTextChannelAsync(string name) + => await CreateTextChannelAsync(name); + async Task IGuild.CreateVoiceChannelAsync(string name) + => await CreateVoiceChannelAsync(name); + + async Task> IGuild.GetIntegrationsAsync() + => await GetIntegrationsAsync(); + async Task IGuild.CreateIntegrationAsync(ulong id, string type) + => await CreateIntegrationAsync(id, type); + + async Task> IGuild.GetInvitesAsync() + => await GetInvitesAsync(); + + IRole IGuild.GetRole(ulong id) + => GetRole(id); + + async Task> IGuild.GetUsersAsync() + => await GetUsersAsync(); + async Task IGuild.GetUserAsync(ulong id) + => await GetUserAsync(id); + IGuildUser IGuild.GetCachedUser(ulong id) + => null; + async Task IGuild.GetCurrentUserAsync() + => await GetCurrentUserAsync(); + Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } + } +} diff --git a/src/Discord.Net/Entities/Guilds/GuildEmbed.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs similarity index 67% rename from src/Discord.Net/Entities/Guilds/GuildEmbed.cs rename to src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs index f912fb076..f26a62d8d 100644 --- a/src/Discord.Net/Entities/Guilds/GuildEmbed.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs @@ -4,18 +4,20 @@ using Model = Discord.API.GuildEmbed; namespace Discord { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public struct GuildEmbed + public struct RestGuildEmbed { public bool IsEnabled { get; private set; } public ulong? ChannelId { get; private set; } - public GuildEmbed(bool isEnabled, ulong? channelId) + internal RestGuildEmbed(bool isEnabled, ulong? channelId) { ChannelId = channelId; IsEnabled = isEnabled; } - internal GuildEmbed(Model model) - : this(model.Enabled, model.ChannelId) { } + internal static RestGuildEmbed Create(Model model) + { + return 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 new file mode 100644 index 000000000..bbbe57a0d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs @@ -0,0 +1,77 @@ +using Discord.API.Rest; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Integration; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestGuildIntegration : RestEntity, IGuildIntegration + { + private long _syncedAtTicks; + + public string Name { get; private set; } + public string Type { get; private set; } + public bool IsEnabled { get; private set; } + public bool IsSyncing { get; private set; } + public ulong ExpireBehavior { get; private set; } + 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; } + + public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); + + internal RestGuildIntegration(DiscordRestClient discord, ulong id) + : base(discord, id) + { + } + internal static RestGuildIntegration Create(DiscordRestClient discord, Model model) + { + var entity = new RestGuildIntegration(discord, model.Id); + entity.Update(model); + return entity; + } + + public void Update(Model model) + { + Name = model.Name; + Type = model.Type; + IsEnabled = model.Enabled; + IsSyncing = model.Syncing; + ExpireBehavior = model.ExpireBehavior; + ExpireGracePeriod = model.ExpireGracePeriod; + _syncedAtTicks = model.SyncedAt.UtcTicks; + + RoleId = model.RoleId; + User = RestUser.Create(Discord, model.User); + } + + 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 ModifyGuildIntegrationParams(); + func(args); + var model = await Discord.ApiClient.ModifyGuildIntegrationAsync(GuildId, Id, args).ConfigureAwait(false); + + Update(model); + } + public async Task SyncAsync() + { + await Discord.ApiClient.SyncGuildIntegrationAsync(GuildId, Id).ConfigureAwait(false); + } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; + + IUser IGuildIntegration.User => User; + } +} diff --git a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs similarity index 69% rename from src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs rename to src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index 3e8818a41..e1f51b4a9 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -1,12 +1,11 @@ -using Discord.Rest; -using System.Diagnostics; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.UserGuild; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class UserGuild : SnowflakeEntity, IUserGuild + public class RestUserGuild : RestEntity, ISnowflakeEntity, IUserGuild { private string _iconId; @@ -14,20 +13,21 @@ namespace Discord.Rest public bool IsOwner { get; private set; } public GuildPermissions Permissions { get; private set; } - public override DiscordRestClient Discord { get; } - public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); - public UserGuild(DiscordRestClient discord, Model model) - : base(model.Id) + internal RestUserGuild(DiscordRestClient discord, ulong id) + : base(discord, id) { - Discord = discord; - Update(model, UpdateSource.Creation); } - public void Update(Model model, UpdateSource source) + internal static RestUserGuild Create(DiscordRestClient discord, Model model) { - if (source == UpdateSource.Rest && IsAttached) return; + var entity = new RestUserGuild(discord, model.Id); + entity.Update(model); + return entity; + } + public void Update(Model model) + { _iconId = model.Icon; IsOwner = model.Owner; Name = model.Name; diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs new file mode 100644 index 000000000..b6e4c2a26 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs @@ -0,0 +1,38 @@ +using Discord.Rest; +using System.Diagnostics; +using Model = Discord.API.VoiceRegion; + +namespace Discord +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class RestVoiceRegion : RestEntity, IVoiceRegion + { + public string Name { get; private set; } + public bool IsVip { get; private set; } + public bool IsOptimal { get; private set; } + public string SampleHostname { get; private set; } + public int SamplePort { get; private set; } + + internal RestVoiceRegion(DiscordRestClient client, string id) + : base(client, id) + { + } + internal static RestVoiceRegion Create(DiscordRestClient client, Model model) + { + var entity = new RestVoiceRegion(client, model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Name = model.Name; + IsVip = model.IsVip; + IsOptimal = model.IsOptimal; + SampleHostname = model.SampleHostname; + SamplePort = model.SamplePort; + } + + 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 new file mode 100644 index 000000000..40bbe3632 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Model = Discord.API.Invite; + +namespace Discord.Rest +{ + internal static class InviteHelper + { + public static async Task GetAsync(IInvite invite, DiscordRestClient client) + { + return await client.ApiClient.GetInviteAsync(invite.Code).ConfigureAwait(false); + } + public static async Task AcceptAsync(IInvite invite, DiscordRestClient client) + { + await client.ApiClient.AcceptInviteAsync(invite.Code).ConfigureAwait(false); + } + public static async Task DeleteAsync(IInvite invite, DiscordRestClient client) + { + await client.ApiClient.DeleteInviteAsync(invite.Code).ConfigureAwait(false); + } + } +} diff --git a/src/Discord.Net/Rest/Entities/Invites/Invite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs similarity index 50% rename from src/Discord.Net/Rest/Entities/Invites/Invite.cs rename to src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index c69fcbad4..84af7e029 100644 --- a/src/Discord.Net/Rest/Entities/Invites/Invite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -1,50 +1,50 @@ -using Discord.Rest; -using System.Diagnostics; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Invite; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class Invite : Entity, IInvite + public class RestInvite : RestEntity, IInvite, IUpdateable { public string ChannelName { get; private set; } public string GuildName { get; private set; } public ulong ChannelId { get; private set; } public ulong GuildId { get; private set; } - public override DiscordRestClient Discord { get; } public string Code => Id; public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; - public Invite(DiscordRestClient discord, Model model) - : base(model.Code) + internal RestInvite(DiscordRestClient discord, string id) + : base(discord, id) { - Discord = discord; - - Update(model, UpdateSource.Creation); } - public void Update(Model model, UpdateSource source) + internal static RestInvite Create(DiscordRestClient discord, Model model) + { + var entity = new RestInvite(discord, model.Code); + entity.Update(model); + return entity; + } + internal void Update(Model model) { - if (source == UpdateSource.Rest && IsAttached) return; - GuildId = model.Guild.Id; ChannelId = model.Channel.Id; GuildName = model.Guild.Name; ChannelName = model.Channel.Name; } - public async Task AcceptAsync() - { - await Discord.ApiClient.AcceptInviteAsync(Code).ConfigureAwait(false); - } - public async Task DeleteAsync() - { - await Discord.ApiClient.DeleteInviteAsync(Code).ConfigureAwait(false); - } + public async Task UpdateAsync() + => Update(await InviteHelper.GetAsync(this, Discord).ConfigureAwait(false)); + public Task DeleteAsync() + => InviteHelper.DeleteAsync(this, Discord); + + public Task AcceptAsync() + => InviteHelper.AcceptAsync(this, Discord); public override string ToString() => Url; private string DebuggerDisplay => $"{Url} ({GuildName} / {ChannelName})"; + + string IEntity.Id => Code; } } diff --git a/src/Discord.Net/Rest/Entities/Invites/InviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs similarity index 50% rename from src/Discord.Net/Rest/Entities/Invites/InviteMetadata.cs rename to src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 05a10514c..138fd6f66 100644 --- a/src/Discord.Net/Rest/Entities/Invites/InviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -1,10 +1,11 @@ -using Discord.Rest; -using System; +using System; +using System.Diagnostics; using Model = Discord.API.InviteMetadata; namespace Discord.Rest { - internal class InviteMetadata : Invite, IInviteMetadata + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestInviteMetadata : RestInvite, IInviteMetadata { private long _createdAtTicks; @@ -13,20 +14,24 @@ namespace Discord.Rest public int? MaxAge { get; private set; } public int? MaxUses { get; private set; } public int Uses { get; private set; } - public IUser Inviter { get; private set; } + public RestUser Inviter { get; private set; } public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); - public InviteMetadata(DiscordRestClient client, Model model) - : base(client, model) + internal RestInviteMetadata(DiscordRestClient discord, string id) + : base(discord, id) { - Update(model, UpdateSource.Creation); } - public void Update(Model model, UpdateSource source) + internal static RestInviteMetadata Create(DiscordRestClient discord, Model model) { - if (source == UpdateSource.Rest && IsAttached) return; - - Inviter = new User(model.Inviter); + var entity = new RestInviteMetadata(discord, model.Code); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + base.Update(model); + Inviter = RestUser.Create(Discord, model.Inviter); IsRevoked = model.Revoked; IsTemporary = model.Temporary; MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; @@ -34,5 +39,7 @@ namespace Discord.Rest Uses = model.Uses; _createdAtTicks = model.CreatedAt.UtcTicks; } + + IUser IInviteMetadata.Inviter => Inviter; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs new file mode 100644 index 000000000..54f0982b1 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -0,0 +1,33 @@ +using Discord.API.Rest; +using System; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + internal static class MessageHelper + { + public static async Task GetAsync(IMessage msg, DiscordRestClient client) + { + await client.ApiClient.GetChannelMessageAsync(msg.ChannelId, msg.Id); + } + public static async Task ModifyAsync(IMessage msg, DiscordRestClient client, Action func) + { + var args = new ModifyMessageParams(); + func(args); + await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args); + } + public static async Task DeleteAsync(IMessage msg, DiscordRestClient client) + { + await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id); + } + + public static async Task PinAsync(IMessage msg, DiscordRestClient client) + { + await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id); + } + public static async Task UnpinAsync(IMessage msg, DiscordRestClient client) + { + await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs b/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs new file mode 100644 index 000000000..c22bf4504 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs @@ -0,0 +1,32 @@ +using Model = Discord.API.Attachment; + +namespace Discord +{ + public class RestAttachment : 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 RestAttachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width) + { + Id = id; + Filename = filename; + Url = url; + ProxyUrl = proxyUrl; + Size = size; + Height = height; + Width = width; + } + internal static RestAttachment Create(Model model) + { + return new RestAttachment(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); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs b/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs new file mode 100644 index 000000000..bc7b09faf --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs @@ -0,0 +1,30 @@ +using Model = Discord.API.Embed; + +namespace Discord +{ + public class RestEmbed : IEmbed + { + public string Description { get; } + public string Url { get; } + public string Title { get; } + public string Type { get; } + public EmbedProvider? Provider { get; } + public EmbedThumbnail? Thumbnail { get; } + + internal RestEmbed(string type, string title, string description, string url, EmbedProvider? provider, EmbedThumbnail? thumbnail) + { + Type = type; + Title = title; + Description = description; + Url = url; + Provider = provider; + Thumbnail = thumbnail; + } + internal static RestEmbed Create(Model model) + { + return new RestEmbed(model.Type, model.Title, model.Description, model.Url, + model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, + model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs new file mode 100644 index 000000000..58ab4c80b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Message; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public abstract class RestMessage : RestEntity, IMessage, IUpdateable + { + private long _timestampTicks; + + public ulong ChannelId { get; } + public IUser Author { get; } + + public string Content { get; private set; } + + 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 MentionedRoles => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + + public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); + + internal RestMessage(DiscordRestClient discord, ulong id, ulong channelId) + : base(discord, id) + { + ChannelId = channelId; + } + internal static RestMessage Create(DiscordRestClient discord, Model model) + { + if (model.Type == MessageType.Default) + return RestUserMessage.Create(discord, model); + else + return RestSystemMessage.Create(discord, model); + } + internal virtual void Update(Model model) + { + if (model.Timestamp.IsSpecified) + _timestampTicks = model.Timestamp.Value.UtcTicks; + + if (model.Content.IsSpecified) + Content = model.Content.Value; + } + + public async Task UpdateAsync() + { + var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id).ConfigureAwait(false); + Update(model); + } + + MessageType IMessage.Type => MessageType.Default; + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs new file mode 100644 index 000000000..3b805696f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using Model = Discord.API.Message; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestSystemMessage : RestMessage, ISystemMessage + { + public MessageType Type { get; private set; } + + internal RestSystemMessage(DiscordRestClient discord, ulong id, ulong channelId) + : base(discord, id, channelId) + { + } + internal new static RestSystemMessage Create(DiscordRestClient discord, Model model) + { + var entity = new RestSystemMessage(discord, model.Id, model.ChannelId); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + + Type = model.Type; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs new file mode 100644 index 000000000..4db6cc372 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -0,0 +1,135 @@ +using Discord.API.Rest; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Message; +using Discord.API; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestUserMessage : RestMessage, IUserMessage + { + private bool _isMentioningEveryone, _isTTS, _isPinned; + private long? _editedTimestampTicks; + private ImmutableArray _attachments; + private ImmutableArray _embeds; + private ImmutableArray _mentionedChannelIds; + private ImmutableArray _mentionedRoles; + private ImmutableArray _mentionedUsers; + + 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 => _mentionedChannelIds; + public override IReadOnlyCollection MentionedRoles => _mentionedRoles; + public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + + internal RestUserMessage(DiscordRestClient discord, ulong id, ulong channelId) + : base(discord, id, channelId) + { + } + internal new static RestUserMessage Create(DiscordRestClient discord, Model model) + { + var entity = new RestUserMessage(discord, model.Id, model.ChannelId); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + base.Update(model); + + if (model.IsTextToSpeech.IsSpecified) + _isTTS = model.IsTextToSpeech.Value; + if (model.Pinned.IsSpecified) + _isPinned = model.Pinned.Value; + if (model.EditedTimestamp.IsSpecified) + _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; + if (model.MentionEveryone.IsSpecified) + _isMentioningEveryone = model.MentionEveryone.Value; + + if (model.Attachments.IsSpecified) + { + var value = model.Attachments.Value; + if (value.Length > 0) + { + var attachments = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + attachments.Add(RestAttachment.Create(value[i])); + _attachments = attachments.ToImmutable(); + } + else + _attachments = ImmutableArray.Create(); + } + + if (model.Embeds.IsSpecified) + { + var value = model.Embeds.Value; + if (value.Length > 0) + { + var embeds = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + embeds.Add(RestEmbed.Create(value[i])); + _embeds = embeds.ToImmutable(); + } + else + _embeds = ImmutableArray.Create(); + } + + ImmutableArray mentions = ImmutableArray.Create(); + if (model.Mentions.IsSpecified) + { + var value = model.Mentions.Value; + if (value.Length > 0) + { + var newMentions = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + newMentions.Add(RestUser.Create(Discord, value[i])); + mentions = newMentions.ToImmutable(); + } + } + + if (model.Content.IsSpecified) + { + var text = model.Content.Value; + + _mentionedUsers = MentionsHelper.GetUserMentions(text, null, mentions); + _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, null); + _mentionedRoles = MentionsHelper.GetRoleMentions(text, null); + model.Content = text; + } + } + + public Task ModifyAsync(Action func) + => MessageHelper.ModifyAsync(this, Discord, func); + public Task DeleteAsync() + => MessageHelper.DeleteAsync(this, Discord); + + public Task PinAsync() + => MessageHelper.PinAsync(this, Discord); + public Task UnpinAsync() + => MessageHelper.UnpinAsync(this, Discord); + + public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, + RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) + => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); + public string Resolve(int startIndex, int length, UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, + RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) + => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); + public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, + RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) + { + text = MentionsHelper.ResolveUserMentions(text, null, MentionedUsers, userHandling); + text = MentionsHelper.ResolveChannelMentions(text, null, channelHandling); + text = MentionsHelper.ResolveRoleMentions(text, MentionedRoles, roleHandling); + text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); + return text; + } + } +} diff --git a/src/Discord.Net/Rest/Entities/Application.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs similarity index 61% rename from src/Discord.Net/Rest/Entities/Application.cs rename to src/Discord.Net.Rest/Entities/RestApplication.cs index 2e743daf4..3131cc96d 100644 --- a/src/Discord.Net/Rest/Entities/Application.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -1,11 +1,10 @@ -using Discord.Rest; -using System; +using System; using System.Threading.Tasks; using Model = Discord.API.Application; namespace Discord.Rest { - internal class Application : SnowflakeEntity, IApplication + public class RestApplication : RestEntity, IApplication { protected string _iconId; @@ -14,39 +13,37 @@ namespace Discord.Rest public string[] RPCOrigins { get; private set; } public ulong Flags { get; private set; } - public override DiscordRestClient Discord { get; } public IUser Owner { get; private set; } public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId); - public Application(DiscordRestClient discord, Model model) - : base(model.Id) + internal RestApplication(DiscordRestClient discord, ulong id) + : base(discord, id) { - Discord = discord; - - Update(model, UpdateSource.Creation); } - - internal void Update(Model model, UpdateSource source) + internal static RestApplication Create(DiscordRestClient discord, Model model) { - if (source == UpdateSource.Rest && IsAttached) return; - + 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; Flags = model.Flags; - Owner = new User(model.Owner); + Owner = RestUser.Create(Discord, model.Owner); _iconId = model.Icon; } public async Task UpdateAsync() { - if (IsAttached) throw new NotSupportedException(); - var response = await Discord.ApiClient.GetMyApplicationAsync().ConfigureAwait(false); if (response.Id != Id) throw new InvalidOperationException("Unable to update this object from a different application token."); - Update(response, UpdateSource.Rest); + Update(response); } } } diff --git a/src/Discord.Net.Rest/Entities/RestEntity.cs b/src/Discord.Net.Rest/Entities/RestEntity.cs new file mode 100644 index 000000000..496dfe9c6 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/RestEntity.cs @@ -0,0 +1,19 @@ +using System; + +namespace Discord.Rest +{ + public abstract class RestEntity : IEntity + where T : IEquatable + { + public T Id { get; } + public DiscordRestClient Discord { get; } + + public RestEntity(DiscordRestClient discord, T id) + { + Discord = discord; + Id = id; + } + + IDiscordClient IEntity.Discord => Discord; + } +} diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs new file mode 100644 index 000000000..ccac5f3ad --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -0,0 +1,52 @@ +using Discord.API.Rest; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Role; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestRole : RestEntity, IRole + { + public RestGuild Guild { get; } + + public Color Color { get; private set; } + public bool IsHoisted { get; private set; } + public bool IsManaged { get; private set; } + public string Name { get; private set; } + public GuildPermissions Permissions { get; private set; } + public int Position { get; private set; } + + public bool IsEveryone => Id == Guild.Id; + public string Mention => MentionUtils.MentionRole(Id); + + internal RestRole(DiscordRestClient discord, ulong id) + : base(discord, id) + { + } + internal static RestRole Create(DiscordRestClient discord, Model model) + { + var entity = new RestRole(discord, model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Name = model.Name; + IsHoisted = model.Hoist; + IsManaged = model.Managed; + Position = model.Position; + Color = new Color(model.Color); + Permissions = new GuildPermissions(model.Permissions); + } + + public Task ModifyAsync(Action func) + => RoleHelper.ModifyAsync(this, Discord, func); + public Task DeleteAsync() + => RoleHelper.DeleteAsync(this, Discord); + + //IRole + IGuild IRole.Guild => Guild; + } +} diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs new file mode 100644 index 000000000..864e81630 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -0,0 +1,22 @@ +using Discord.API.Rest; +using System; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + internal static class RoleHelper + { + //General + public static async Task DeleteAsync(IRole role, DiscordRestClient client) + { + await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id).ConfigureAwait(false); + } + public static async Task ModifyAsync(IRole role, DiscordRestClient client, + Action func) + { + var args = new ModifyGuildRoleParams(); + func(args); + await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, args); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs new file mode 100644 index 000000000..a369edd96 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Model = Discord.API.Connection; + +namespace Discord +{ + [DebuggerDisplay(@"{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) + { + Id = id; + Type = type; + Name = name; + IsRevoked = isRevoked; + + 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 = {Type}{(IsRevoked ? ", Revoked" : "")})"; + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs new file mode 100644 index 000000000..a8ea34550 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using Model = Discord.API.User; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestGroupUser : RestUser, IGroupUser + { + internal RestGroupUser(DiscordRestClient discord, ulong id) + : base(discord, id) + { + } + internal new static RestGroupUser Create(DiscordRestClient discord, Model model) + { + var entity = new RestGroupUser(discord, model.Id); + entity.Update(model); + return entity; + } + + //IVoiceState + bool IVoiceState.IsDeafened => false; + bool IVoiceState.IsMuted => false; + bool IVoiceState.IsSelfDeafened => false; + bool IVoiceState.IsSelfMuted => false; + bool IVoiceState.IsSuppressed => false; + IVoiceChannel IVoiceState.VoiceChannel => null; + string IVoiceState.VoiceSessionId => null; + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs new file mode 100644 index 000000000..c2e25f735 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -0,0 +1,74 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.GuildMember; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestGuildUser : RestUser, IGuildUser, IUpdateable + { + private long? _joinedAtTicks; + private ImmutableArray _roleIds; + + public string Nickname { get; private set; } + public ulong GuildId { get; private set; } + + public IReadOnlyCollection RoleIds => _roleIds; + + public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); + + internal RestGuildUser(DiscordRestClient discord, ulong id) + : base(discord, id) + { + } + internal static RestGuildUser Create(DiscordRestClient discord, Model model) + { + var entity = new RestGuildUser(discord, model.User.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + _joinedAtTicks = model.JoinedAt.UtcTicks; + if (model.Nick.IsSpecified) + Nickname = model.Nick.Value; + UpdateRoles(model.Roles); + } + private void UpdateRoles(ulong[] roleIds) + { + var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1); + roles.Add(GuildId); + for (int i = 0; i < roleIds.Length; i++) + roles.Add(roleIds[i]); + _roleIds = roles.ToImmutable(); + } + + public override async Task UpdateAsync() + => Update(await UserHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) + => UserHelper.ModifyAsync(this, Discord, func); + public Task KickAsync() + => UserHelper.KickAsync(this, Discord); + + public ChannelPermissions GetPermissions(IGuildChannel channel) + { + throw new NotImplementedException(); //TODO: Impl + } + + //IGuildUser + IReadOnlyCollection IGuildUser.RoleIds => RoleIds; + + //IVoiceState + bool IVoiceState.IsDeafened => false; + bool IVoiceState.IsMuted => false; + bool IVoiceState.IsSelfDeafened => false; + bool IVoiceState.IsSelfMuted => false; + bool IVoiceState.IsSuppressed => false; + IVoiceChannel IVoiceState.VoiceChannel => null; + string IVoiceState.VoiceSessionId => null; + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs new file mode 100644 index 000000000..389a2d57d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -0,0 +1,45 @@ +using Discord.API.Rest; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.User; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestSelfUser : RestUser, ISelfUser + { + public string Email { get; private set; } + public bool IsVerified { get; private set; } + public bool IsMfaEnabled { get; private set; } + + internal RestSelfUser(DiscordRestClient discord, ulong id) + : base(discord, id) + { + } + internal new static RestSelfUser Create(DiscordRestClient discord, Model model) + { + var entity = new RestSelfUser(discord, model.Id); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + + if (model.Email.IsSpecified) + Email = model.Email.Value; + if (model.Verified.IsSpecified) + IsVerified = model.Verified.Value; + if (model.MfaEnabled.IsSpecified) + IsMfaEnabled = model.MfaEnabled.Value; + } + + public override async Task UpdateAsync() + => Update(await UserHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) + => UserHelper.ModifyAsync(this, Discord, func); + + Task ISelfUser.ModifyStatusAsync(Action func) { throw new NotSupportedException(); } + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs new file mode 100644 index 000000000..05e4aa3b2 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -0,0 +1,51 @@ +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.User; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestUser : RestEntity, IUser, IUpdateable + { + public bool IsBot { get; private set; } + public string Username { get; private set; } + public ushort DiscriminatorValue { get; private set; } + public string AvatarId { get; private set; } + + public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); + public string Discriminator => DiscriminatorValue.ToString("D4"); + public string Mention => MentionUtils.MentionUser(Id); + public virtual Game? Game => null; + public virtual UserStatus Status => UserStatus.Unknown; + + internal RestUser(DiscordRestClient discord, ulong id) + : base(discord, id) + { + } + internal static RestUser Create(DiscordRestClient discord, Model model) + { + var entity = new RestUser(discord, model.Id); + entity.Update(model); + return entity; + } + internal virtual void Update(Model model) + { + if (model.Avatar.IsSpecified) + AvatarId = model.Avatar.Value; + if (model.Discriminator.IsSpecified) + DiscriminatorValue = ushort.Parse(model.Discriminator.Value); + if (model.Bot.IsSpecified) + IsBot = model.Bot.Value; + if (model.Username.IsSpecified) + Username = model.Username.Value; + } + + public virtual async Task UpdateAsync() + => Update(await UserHelper.GetAsync(this, Discord)); + + public Task CreateDMChannelAsync() + => UserHelper.CreateDMChannelAsync(this, Discord); + + IDMChannel IUser.GetCachedDMChannel() => null; + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs new file mode 100644 index 000000000..08a59f2c0 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -0,0 +1,53 @@ +using Discord.API.Rest; +using System.Threading.Tasks; +using Model = Discord.API.User; +using MemberModel = Discord.API.GuildMember; +using System; + +namespace Discord.Rest +{ + internal static class UserHelper + { + public static async Task GetAsync(IUser user, DiscordRestClient client) + { + return await client.ApiClient.GetUserAsync(user.Id); + } + public static async Task GetAsync(ISelfUser user, DiscordRestClient client) + { + var model = await client.ApiClient.GetMyUserAsync(); + if (model.Id != user.Id) + throw new InvalidOperationException("Unable to update this object using a different token."); + return model; + } + public static async Task GetAsync(IGuildUser user, DiscordRestClient client) + { + return await client.ApiClient.GetGuildMemberAsync(user.GuildId, user.Id); + } + public static async Task ModifyAsync(ISelfUser user, DiscordRestClient client, Action func) + { + if (user.Id != client.CurrentUser.Id) + throw new InvalidOperationException("Unable to modify this object using a different token."); + + var args = new ModifyCurrentUserParams(); + func(args); + await client.ApiClient.ModifySelfAsync(args); + } + public static async Task ModifyAsync(IGuildUser user, DiscordRestClient client, Action func) + { + var args = new ModifyGuildMemberParams(); + func(args); + await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args); + } + + public static async Task KickAsync(IGuildUser user, DiscordRestClient client) + { + await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); + } + + public static async Task CreateDMChannelAsync(IUser user, DiscordRestClient client) + { + var args = new CreateDMChannelParams(user.Id); + return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); + } + } +} diff --git a/src/Discord.Net.Rest/Properties/AssemblyInfo.cs b/src/Discord.Net.Rest/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..f6a5354d2 --- /dev/null +++ b/src/Discord.Net.Rest/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Discord.Net.Rest")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bfc6dc28-0351-4573-926a-d4124244c04f")] diff --git a/src/Discord.Net.Rest/project.json b/src/Discord.Net.Rest/project.json new file mode 100644 index 000000000..330cf0beb --- /dev/null +++ b/src/Discord.Net.Rest/project.json @@ -0,0 +1,39 @@ +{ + "version": "1.0.0-*", + + "buildOptions": { + "compile": { + "include": [ "../Discord.Net.Utils/**.cs" ] + }, + "define": [ "REST" ] + }, + + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } + } + }, + + "dependencies": { + "Discord.Net.Core": { + "target": "project" + }, + "NETStandard.Library": "1.6.0" + }, + + "frameworks": { + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } + } +} diff --git a/src/Discord.Net/API/DiscordRpcAPIClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs similarity index 99% rename from src/Discord.Net/API/DiscordRpcAPIClient.cs rename to src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index 37a2aa5a6..b128f13b0 100644 --- a/src/Discord.Net/API/DiscordRpcAPIClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -3,7 +3,6 @@ using Discord.API.Rpc; using Discord.Net.Queue; using Discord.Net.Rest; using Discord.Net.WebSockets; -using Discord.Rpc; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; diff --git a/src/Discord.Net/API/Rpc/Application.cs b/src/Discord.Net.Rpc/API/Rpc/Application.cs similarity index 100% rename from src/Discord.Net/API/Rpc/Application.cs rename to src/Discord.Net.Rpc/API/Rpc/Application.cs diff --git a/src/Discord.Net/API/Rpc/AuthenticateParams.cs b/src/Discord.Net.Rpc/API/Rpc/AuthenticateParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/AuthenticateParams.cs rename to src/Discord.Net.Rpc/API/Rpc/AuthenticateParams.cs diff --git a/src/Discord.Net/API/Rpc/AuthenticateResponse.cs b/src/Discord.Net.Rpc/API/Rpc/AuthenticateResponse.cs similarity index 100% rename from src/Discord.Net/API/Rpc/AuthenticateResponse.cs rename to src/Discord.Net.Rpc/API/Rpc/AuthenticateResponse.cs diff --git a/src/Discord.Net/API/Rpc/AuthorizeParams.cs b/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/AuthorizeParams.cs rename to src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs diff --git a/src/Discord.Net/API/Rpc/AuthorizeResponse.cs b/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs similarity index 100% rename from src/Discord.Net/API/Rpc/AuthorizeResponse.cs rename to src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs diff --git a/src/Discord.Net/API/Rpc/ChannelSubscriptionParams.cs b/src/Discord.Net.Rpc/API/Rpc/ChannelSubscriptionParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/ChannelSubscriptionParams.cs rename to src/Discord.Net.Rpc/API/Rpc/ChannelSubscriptionParams.cs diff --git a/src/Discord.Net/API/Rpc/ErrorEvent.cs b/src/Discord.Net.Rpc/API/Rpc/ErrorEvent.cs similarity index 100% rename from src/Discord.Net/API/Rpc/ErrorEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/ErrorEvent.cs diff --git a/src/Discord.Net/API/Rpc/GetChannelParams.cs b/src/Discord.Net.Rpc/API/Rpc/GetChannelParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/GetChannelParams.cs rename to src/Discord.Net.Rpc/API/Rpc/GetChannelParams.cs diff --git a/src/Discord.Net/API/Rpc/GetChannelsParams.cs b/src/Discord.Net.Rpc/API/Rpc/GetChannelsParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/GetChannelsParams.cs rename to src/Discord.Net.Rpc/API/Rpc/GetChannelsParams.cs diff --git a/src/Discord.Net/API/Rpc/GetChannelsResponse.cs b/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs similarity index 100% rename from src/Discord.Net/API/Rpc/GetChannelsResponse.cs rename to src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs diff --git a/src/Discord.Net/API/Rpc/GetGuildParams.cs b/src/Discord.Net.Rpc/API/Rpc/GetGuildParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/GetGuildParams.cs rename to src/Discord.Net.Rpc/API/Rpc/GetGuildParams.cs diff --git a/src/Discord.Net/API/Rpc/GetGuildsParams.cs b/src/Discord.Net.Rpc/API/Rpc/GetGuildsParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/GetGuildsParams.cs rename to src/Discord.Net.Rpc/API/Rpc/GetGuildsParams.cs diff --git a/src/Discord.Net/API/Rpc/GetGuildsResponse.cs b/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs similarity index 100% rename from src/Discord.Net/API/Rpc/GetGuildsResponse.cs rename to src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs diff --git a/src/Discord.Net/API/Rpc/GuildStatusEvent.cs b/src/Discord.Net.Rpc/API/Rpc/GuildStatusEvent.cs similarity index 100% rename from src/Discord.Net/API/Rpc/GuildStatusEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/GuildStatusEvent.cs diff --git a/src/Discord.Net/API/Rpc/GuildSubscriptionParams.cs b/src/Discord.Net.Rpc/API/Rpc/GuildSubscriptionParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/GuildSubscriptionParams.cs rename to src/Discord.Net.Rpc/API/Rpc/GuildSubscriptionParams.cs diff --git a/src/Discord.Net/API/Rpc/MessageEvent.cs b/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs similarity index 100% rename from src/Discord.Net/API/Rpc/MessageEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs diff --git a/src/Discord.Net/API/Rpc/ReadyEvent.cs b/src/Discord.Net.Rpc/API/Rpc/ReadyEvent.cs similarity index 100% rename from src/Discord.Net/API/Rpc/ReadyEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/ReadyEvent.cs diff --git a/src/Discord.Net/API/Rpc/RpcChannel.cs b/src/Discord.Net.Rpc/API/Rpc/RpcChannel.cs similarity index 100% rename from src/Discord.Net/API/Rpc/RpcChannel.cs rename to src/Discord.Net.Rpc/API/Rpc/RpcChannel.cs diff --git a/src/Discord.Net/API/Rpc/RpcConfig.cs b/src/Discord.Net.Rpc/API/Rpc/RpcConfig.cs similarity index 100% rename from src/Discord.Net/API/Rpc/RpcConfig.cs rename to src/Discord.Net.Rpc/API/Rpc/RpcConfig.cs diff --git a/src/Discord.Net/API/Rpc/RpcGuild.cs b/src/Discord.Net.Rpc/API/Rpc/RpcGuild.cs similarity index 100% rename from src/Discord.Net/API/Rpc/RpcGuild.cs rename to src/Discord.Net.Rpc/API/Rpc/RpcGuild.cs diff --git a/src/Discord.Net/API/Rpc/RpcMessage.cs b/src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs similarity index 100% rename from src/Discord.Net/API/Rpc/RpcMessage.cs rename to src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs diff --git a/src/Discord.Net/API/Rpc/RpcUserGuild.cs b/src/Discord.Net.Rpc/API/Rpc/RpcUserGuild.cs similarity index 100% rename from src/Discord.Net/API/Rpc/RpcUserGuild.cs rename to src/Discord.Net.Rpc/API/Rpc/RpcUserGuild.cs diff --git a/src/Discord.Net/API/Rpc/SelectVoiceChannelParams.cs b/src/Discord.Net.Rpc/API/Rpc/SelectVoiceChannelParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/SelectVoiceChannelParams.cs rename to src/Discord.Net.Rpc/API/Rpc/SelectVoiceChannelParams.cs diff --git a/src/Discord.Net/API/Rpc/SetLocalVolumeParams.cs b/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeParams.cs similarity index 100% rename from src/Discord.Net/API/Rpc/SetLocalVolumeParams.cs rename to src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeParams.cs diff --git a/src/Discord.Net/API/Rpc/SetLocalVolumeResponse.cs b/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeResponse.cs similarity index 100% rename from src/Discord.Net/API/Rpc/SetLocalVolumeResponse.cs rename to src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeResponse.cs diff --git a/src/Discord.Net/API/Rpc/SpeakingEvent.cs b/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs similarity index 100% rename from src/Discord.Net/API/Rpc/SpeakingEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs diff --git a/src/Discord.Net/API/Rpc/SubscriptionResponse.cs b/src/Discord.Net.Rpc/API/Rpc/SubscriptionResponse.cs similarity index 100% rename from src/Discord.Net/API/Rpc/SubscriptionResponse.cs rename to src/Discord.Net.Rpc/API/Rpc/SubscriptionResponse.cs diff --git a/src/Discord.Net/API/Rpc/VoiceStateEvent.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs similarity index 100% rename from src/Discord.Net/API/Rpc/VoiceStateEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs diff --git a/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj b/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj new file mode 100644 index 000000000..c6ea6658f --- /dev/null +++ b/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 5688a353-121e-40a1-8bfa-b17b91fb48fb + Discord.Net.Rpc + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/src/Discord.Net/Rpc/DiscordRpcClient.Events.cs b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs similarity index 100% rename from src/Discord.Net/Rpc/DiscordRpcClient.Events.cs rename to src/Discord.Net.Rpc/DiscordRpcClient.Events.cs diff --git a/src/Discord.Net/Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs similarity index 100% rename from src/Discord.Net/Rpc/DiscordRpcClient.cs rename to src/Discord.Net.Rpc/DiscordRpcClient.cs diff --git a/src/Discord.Net/Rpc/DiscordRpcConfig.cs b/src/Discord.Net.Rpc/DiscordRpcConfig.cs similarity index 100% rename from src/Discord.Net/Rpc/DiscordRpcConfig.cs rename to src/Discord.Net.Rpc/DiscordRpcConfig.cs diff --git a/src/Discord.Net/Rpc/Entities/IRemoteUserGuild.cs b/src/Discord.Net.Rpc/Entities/IRemoteUserGuild.cs similarity index 100% rename from src/Discord.Net/Rpc/Entities/IRemoteUserGuild.cs rename to src/Discord.Net.Rpc/Entities/IRemoteUserGuild.cs diff --git a/src/Discord.Net/Rpc/Entities/RemoteUserGuild.cs b/src/Discord.Net.Rpc/Entities/RemoteUserGuild.cs similarity index 78% rename from src/Discord.Net/Rpc/Entities/RemoteUserGuild.cs rename to src/Discord.Net.Rpc/Entities/RemoteUserGuild.cs index e0337237a..98bb1c027 100644 --- a/src/Discord.Net/Rpc/Entities/RemoteUserGuild.cs +++ b/src/Discord.Net.Rpc/Entities/RemoteUserGuild.cs @@ -16,12 +16,10 @@ namespace Discord.Rpc { Id = model.Id; Discord = discord; - Update(model, UpdateSource.Creation); + Update(model); } - public void Update(Model model, UpdateSource source) + public void Update(Model model) { - if (source == UpdateSource.Rest) return; - Name = model.Name; } diff --git a/src/Discord.Net/Rpc/Entities/RpcMessage.cs b/src/Discord.Net.Rpc/Entities/RpcMessage.cs similarity index 100% rename from src/Discord.Net/Rpc/Entities/RpcMessage.cs rename to src/Discord.Net.Rpc/Entities/RpcMessage.cs diff --git a/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs b/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..9cfcc4aa1 --- /dev/null +++ b/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Discord.Net.Rpc")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5688a353-121e-40a1-8bfa-b17b91fb48fb")] diff --git a/src/Discord.Net/Rpc/RpcChannelEvent.cs b/src/Discord.Net.Rpc/RpcChannelEvent.cs similarity index 100% rename from src/Discord.Net/Rpc/RpcChannelEvent.cs rename to src/Discord.Net.Rpc/RpcChannelEvent.cs diff --git a/src/Discord.Net/Rpc/RpcGuildEvent.cs b/src/Discord.Net.Rpc/RpcGuildEvent.cs similarity index 100% rename from src/Discord.Net/Rpc/RpcGuildEvent.cs rename to src/Discord.Net.Rpc/RpcGuildEvent.cs diff --git a/src/Discord.Net.Rpc/project.json b/src/Discord.Net.Rpc/project.json new file mode 100644 index 000000000..de1e3725c --- /dev/null +++ b/src/Discord.Net.Rpc/project.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0-*", + + "buildOptions": { + "compile": { + "include": [ "../Discord.Net.Entities/**.cs", "../Discord.Net.Utils/**.cs" ] + }, + "define": [ "RPC" ] + }, + + "dependencies": { + "NETStandard.Library": "1.6.0" + }, + + "frameworks": { + "netstandard1.6": { + "imports": "dnxcore50" + } + } +} diff --git a/src/Discord.Net/Utilities/AsyncEvent.cs b/src/Discord.Net.Utils/AsyncEvent.cs similarity index 100% rename from src/Discord.Net/Utilities/AsyncEvent.cs rename to src/Discord.Net.Utils/AsyncEvent.cs diff --git a/src/Discord.Net/Utilities/ConcurrentHashSet.cs b/src/Discord.Net.Utils/ConcurrentHashSet.cs similarity index 100% rename from src/Discord.Net/Utilities/ConcurrentHashSet.cs rename to src/Discord.Net.Utils/ConcurrentHashSet.cs diff --git a/src/Discord.Net/Utilities/DateTimeUtils.cs b/src/Discord.Net.Utils/DateTimeUtils.cs similarity index 100% rename from src/Discord.Net/Utilities/DateTimeUtils.cs rename to src/Discord.Net.Utils/DateTimeUtils.cs diff --git a/src/Discord.Net.Utils/Discord.Net.Utils.projitems b/src/Discord.Net.Utils/Discord.Net.Utils.projitems new file mode 100644 index 000000000..2a493f22a --- /dev/null +++ b/src/Discord.Net.Utils/Discord.Net.Utils.projitems @@ -0,0 +1,26 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 2b75119c-9893-4aaa-8d38-6176eeb09060 + + + Discord + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Discord.Net.Utils/Discord.Net.Utils.shproj b/src/Discord.Net.Utils/Discord.Net.Utils.shproj new file mode 100644 index 000000000..e3beed6a1 --- /dev/null +++ b/src/Discord.Net.Utils/Discord.Net.Utils.shproj @@ -0,0 +1,13 @@ + + + + 2b75119c-9893-4aaa-8d38-6176eeb09060 + 14.0 + + + + + + + + diff --git a/src/Discord.Net/Extensions/CollectionExtensions.cs b/src/Discord.Net.Utils/Extensions/CollectionExtensions.cs similarity index 100% rename from src/Discord.Net/Extensions/CollectionExtensions.cs rename to src/Discord.Net.Utils/Extensions/CollectionExtensions.cs diff --git a/src/Discord.Net.Utils/Extensions/Permissions.cs b/src/Discord.Net.Utils/Extensions/Permissions.cs new file mode 100644 index 000000000..b8593a31d --- /dev/null +++ b/src/Discord.Net.Utils/Extensions/Permissions.cs @@ -0,0 +1,152 @@ +using System.Runtime.CompilerServices; + +namespace Discord.Extensions +{ + internal static class Permissions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission bit) + => GetValue(allow, deny, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PermValue GetValue(ulong allow, ulong deny, GuildPermission bit) + => GetValue(allow, deny, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PermValue GetValue(ulong allow, ulong deny, byte bit) + { + if (HasBit(allow, bit)) + return PermValue.Allow; + else if (HasBit(deny, bit)) + return PermValue.Deny; + else + return PermValue.Inherit; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GetValue(ulong value, ChannelPermission bit) + => GetValue(value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GetValue(ulong value, GuildPermission bit) + => GetValue(value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GetValue(ulong value, byte bit) => HasBit(value, bit); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission bit) + => SetValue(ref rawValue, value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong rawValue, bool? value, GuildPermission bit) + => SetValue(ref rawValue, value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong rawValue, bool? value, byte bit) + { + if (value.HasValue) + { + if (value == true) + SetBit(ref rawValue, bit); + else + UnsetBit(ref rawValue, bit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission bit) + => SetValue(ref allow, ref deny, value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission bit) + => SetValue(ref allow, ref deny, value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, byte bit) + { + if (value.HasValue) + { + switch (value) + { + case PermValue.Allow: + SetBit(ref allow, bit); + UnsetBit(ref deny, bit); + break; + case PermValue.Deny: + UnsetBit(ref allow, bit); + SetBit(ref deny, bit); + break; + default: + UnsetBit(ref allow, bit); + UnsetBit(ref deny, bit); + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasBit(ulong value, byte bit) => (value & (1U << bit)) != 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetBit(ref ulong value, byte bit) => value |= (1U << bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); + + public static ulong ResolveGuild(IGuild guild, IGuildUser user) + { + ulong resolvedPermissions = 0; + + if (user.Id == guild.OwnerId) + resolvedPermissions = GuildPermissions.All.RawValue; //Owners always have all permissions + else + { + foreach (var role in user.RoleIds) + resolvedPermissions |= guild.GetRole(role).Permissions.RawValue; + if (GetValue(resolvedPermissions, GuildPermission.Administrator)) + resolvedPermissions = GuildPermissions.All.RawValue; //Administrators always have all permissions + } + return resolvedPermissions; + } + + /*public static ulong ResolveChannel(IGuildUser user, IGuildChannel channel) + { + return ResolveChannel(user, channel, ResolveGuild(user)); + }*/ + public static ulong ResolveChannel(IGuild guild, IGuildChannel channel, IGuildUser user, ulong guildPermissions) + { + ulong resolvedPermissions = 0; + + ulong mask = ChannelPermissions.All(channel).RawValue; + if (/*user.Id == user.Guild.OwnerId || */GetValue(guildPermissions, GuildPermission.Administrator)) + resolvedPermissions = mask; //Owners and administrators always have all permissions + else + { + //Start with this user's guild permissions + resolvedPermissions = guildPermissions; + + OverwritePermissions? perms; + var roleIds = user.RoleIds; + if (roleIds.Count > 0) + { + ulong deniedPermissions = 0UL, allowedPermissions = 0UL; + foreach (var roleId in roleIds) + { + perms = channel.GetPermissionOverwrite(guild.GetRole(roleId)); + if (perms != null) + { + deniedPermissions |= perms.Value.DenyValue; + allowedPermissions |= perms.Value.AllowValue; + } + } + resolvedPermissions = (resolvedPermissions & ~deniedPermissions) | allowedPermissions; + } + perms = channel.GetPermissionOverwrite(user); + if (perms != null) + resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; + + //TODO: C#7 Typeswitch candidate + var textChannel = channel as ITextChannel; + var voiceChannel = channel as IVoiceChannel; + if (textChannel != null && !GetValue(resolvedPermissions, ChannelPermission.ReadMessages)) + resolvedPermissions = 0; //No read permission on a text channel removes all other permissions + else if (voiceChannel != null && !GetValue(resolvedPermissions, ChannelPermission.Connect)) + resolvedPermissions = 0; //No connect permission on a voice channel removes all other permissions + resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) + } + + return resolvedPermissions; + } + } +} diff --git a/src/Discord.Net/Extensions/TaskCompletionSourceExtensions.cs b/src/Discord.Net.Utils/Extensions/TaskCompletionSourceExtensions.cs similarity index 58% rename from src/Discord.Net/Extensions/TaskCompletionSourceExtensions.cs rename to src/Discord.Net.Utils/Extensions/TaskCompletionSourceExtensions.cs index a5a715b4c..be7c42e9c 100644 --- a/src/Discord.Net/Extensions/TaskCompletionSourceExtensions.cs +++ b/src/Discord.Net.Utils/Extensions/TaskCompletionSourceExtensions.cs @@ -5,18 +5,10 @@ namespace Discord { internal static class TaskCompletionSourceExtensions { - 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/Logging/LogManager.cs b/src/Discord.Net.Utils/Logging/LogManager.cs similarity index 55% rename from src/Discord.Net/Logging/LogManager.cs rename to src/Discord.Net.Utils/Logging/LogManager.cs index e37b2bce6..dfcc97d33 100644 --- a/src/Discord.Net/Logging/LogManager.cs +++ b/src/Discord.Net.Utils/Logging/LogManager.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; namespace Discord.Logging { - internal class LogManager : ILogManager, ILogger + internal class LogManager { public LogSeverity Level { get; } @@ -30,21 +30,6 @@ namespace Discord.Logging if (severity <= Level) await _messageEvent.InvokeAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); } - async Task ILogger.LogAsync(LogSeverity severity, string message, Exception ex) - { - if (severity <= Level) - await _messageEvent.InvokeAsync(new LogMessage(severity, "Discord", message, ex)).ConfigureAwait(false); - } - async Task ILogger.LogAsync(LogSeverity severity, FormattableString message, Exception ex) - { - if (severity <= Level) - await _messageEvent.InvokeAsync(new LogMessage(severity, "Discord", message.ToString(), ex)).ConfigureAwait(false); - } - async Task ILogger.LogAsync(LogSeverity severity, Exception ex) - { - if (severity <= Level) - await _messageEvent.InvokeAsync(new LogMessage(severity, "Discord", null, ex)).ConfigureAwait(false); - } public Task ErrorAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Error, source, message, ex); @@ -52,12 +37,6 @@ namespace Discord.Logging => LogAsync(LogSeverity.Error, source, message, ex); public Task ErrorAsync(string source, Exception ex) => LogAsync(LogSeverity.Error, source, ex); - Task ILogger.ErrorAsync(string message, Exception ex) - => LogAsync(LogSeverity.Error, "Discord", message, ex); - Task ILogger.ErrorAsync(FormattableString message, Exception ex) - => LogAsync(LogSeverity.Error, "Discord", message, ex); - Task ILogger.ErrorAsync(Exception ex) - => LogAsync(LogSeverity.Error, "Discord", ex); public Task WarningAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Warning, source, message, ex); @@ -65,12 +44,6 @@ namespace Discord.Logging => LogAsync(LogSeverity.Warning, source, message, ex); public Task WarningAsync(string source, Exception ex) => LogAsync(LogSeverity.Warning, source, ex); - Task ILogger.WarningAsync(string message, Exception ex) - => LogAsync(LogSeverity.Warning, "Discord", message, ex); - Task ILogger.WarningAsync(FormattableString message, Exception ex) - => LogAsync(LogSeverity.Warning, "Discord", message, ex); - Task ILogger.WarningAsync(Exception ex) - => LogAsync(LogSeverity.Warning, "Discord", ex); public Task InfoAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Info, source, message, ex); @@ -78,12 +51,6 @@ namespace Discord.Logging => LogAsync(LogSeverity.Info, source, message, ex); public Task InfoAsync(string source, Exception ex) => LogAsync(LogSeverity.Info, source, ex); - Task ILogger.InfoAsync(string message, Exception ex) - => LogAsync(LogSeverity.Info, "Discord", message, ex); - Task ILogger.InfoAsync(FormattableString message, Exception ex) - => LogAsync(LogSeverity.Info, "Discord", message, ex); - Task ILogger.InfoAsync(Exception ex) - => LogAsync(LogSeverity.Info, "Discord", ex); public Task VerboseAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Verbose, source, message, ex); @@ -91,12 +58,6 @@ namespace Discord.Logging => LogAsync(LogSeverity.Verbose, source, message, ex); public Task VerboseAsync(string source, Exception ex) => LogAsync(LogSeverity.Verbose, source, ex); - Task ILogger.VerboseAsync(string message, Exception ex) - => LogAsync(LogSeverity.Verbose, "Discord", message, ex); - Task ILogger.VerboseAsync(FormattableString message, Exception ex) - => LogAsync(LogSeverity.Verbose, "Discord", message, ex); - Task ILogger.VerboseAsync(Exception ex) - => LogAsync(LogSeverity.Verbose, "Discord", ex); public Task DebugAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Debug, source, message, ex); @@ -104,13 +65,7 @@ namespace Discord.Logging => LogAsync(LogSeverity.Debug, source, message, ex); public Task DebugAsync(string source, Exception ex) => LogAsync(LogSeverity.Debug, source, ex); - Task ILogger.DebugAsync(string message, Exception ex) - => LogAsync(LogSeverity.Debug, "Discord", message, ex); - Task ILogger.DebugAsync(FormattableString message, Exception ex) - => LogAsync(LogSeverity.Debug, "Discord", message, ex); - Task ILogger.DebugAsync(Exception ex) - => LogAsync(LogSeverity.Debug, "Discord", ex); - public ILogger CreateLogger(string name) => new Logger(this, name); + public Logger CreateLogger(string name) => new Logger(this, name); } } diff --git a/src/Discord.Net/Logging/Logger.cs b/src/Discord.Net.Utils/Logging/Logger.cs similarity index 98% rename from src/Discord.Net/Logging/Logger.cs rename to src/Discord.Net.Utils/Logging/Logger.cs index 2255f4451..c871c0b26 100644 --- a/src/Discord.Net/Logging/Logger.cs +++ b/src/Discord.Net.Utils/Logging/Logger.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; namespace Discord.Logging { - internal class Logger : ILogger + internal class Logger { private readonly LogManager _manager; diff --git a/src/Discord.Net/Utilities/MentionUtils.cs b/src/Discord.Net.Utils/MentionsHelper.cs similarity index 56% rename from src/Discord.Net/Utilities/MentionUtils.cs rename to src/Discord.Net.Utils/MentionsHelper.cs index dc2aeb6c2..58c0fe16d 100644 --- a/src/Discord.Net/Utilities/MentionUtils.cs +++ b/src/Discord.Net.Utils/MentionsHelper.cs @@ -1,6 +1,4 @@ -using Discord.WebSocket; -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; using System.Linq; @@ -8,112 +6,35 @@ using System.Text.RegularExpressions; namespace Discord { - public static class MentionUtils + internal static class MentionsHelper { private static readonly Regex _userRegex = new Regex(@"<@!?([0-9]+)>", RegexOptions.Compiled); private static readonly Regex _channelRegex = new Regex(@"<#([0-9]+)>", RegexOptions.Compiled); private static readonly Regex _roleRegex = new Regex(@"<@&([0-9]+)>", RegexOptions.Compiled); //If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake) - internal static string Mention(IUser user, bool useNickname = true) => useNickname ? $"<@!{user.Id}>" : $"<@{user.Id}>"; - public static string MentionUser(ulong id) => $"<@!{id}>"; + internal static string MentionUser(ulong id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>"; + internal static string MentionChannel(ulong id) => $"<#{id}>"; + internal static string MentionRole(ulong id) => $"<@&{id}>"; - internal static string Mention(IChannel channel) => $"<#{channel.Id}>"; - public static string MentionChannel(ulong id) => $"<#{id}>"; - - internal static string Mention(IRole role) => $"<@&{role.Id}>"; - public static string MentionRole(ulong id) => $"<@&{id}>"; - - /// Parses a provided user mention string. - public static ulong ParseUser(string mentionText) - { - ulong id; - if (TryParseUser(mentionText, out id)) - return id; - throw new ArgumentException("Invalid mention format", nameof(mentionText)); - } - /// Tries to parse a provided user mention string. - public static bool TryParseUser(string mentionText, out ulong userId) - { - mentionText = mentionText.Trim(); - if (mentionText.Length >= 3 && mentionText[0] == '<' && mentionText[1] == '@' && mentionText[mentionText.Length - 1] == '>') - { - if (mentionText.Length >= 4 && mentionText[2] == '!') - mentionText = mentionText.Substring(3, mentionText.Length - 4); //<@!123> - else - mentionText = mentionText.Substring(2, mentionText.Length - 3); //<@123> - - if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out userId)) - return true; - } - userId = 0; - return false; - } - - /// Parses a provided channel mention string. - public static ulong ParseChannel(string mentionText) - { - ulong id; - if (TryParseChannel(mentionText, out id)) - return id; - throw new ArgumentException("Invalid mention format", nameof(mentionText)); - } - /// Tries to parse a provided channel mention string. - public static bool TryParseChannel(string mentionText, out ulong channelId) - { - mentionText = mentionText.Trim(); - if (mentionText.Length >= 3 && mentionText[0] == '<' && mentionText[1] == '#' && mentionText[mentionText.Length - 1] == '>') - { - mentionText = mentionText.Substring(2, mentionText.Length - 3); //<#123> - - if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out channelId)) - return true; - } - channelId = 0; - return false; - } - - /// Parses a provided role mention string. - public static ulong ParseRole(string mentionText) - { - ulong id; - if (TryParseRole(mentionText, out id)) - return id; - throw new ArgumentException("Invalid mention format", nameof(mentionText)); - } - /// Tries to parse a provided role mention string. - public static bool TryParseRole(string mentionText, out ulong roleId) - { - mentionText = mentionText.Trim(); - if (mentionText.Length >= 4 && mentionText[0] == '<' && mentionText[1] == '@' && mentionText[2] == '&' && mentionText[mentionText.Length - 1] == '>') - { - mentionText = mentionText.Substring(3, mentionText.Length - 4); //<@&123> - - if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out roleId)) - return true; - } - roleId = 0; - return false; - } - - internal static ImmutableArray GetUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentionedUsers) + internal static ImmutableArray GetUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentionedUsers) + where TUser : class, IUser { var matches = _userRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); + var builder = ImmutableArray.CreateBuilder(matches.Count); foreach (var match in matches.OfType()) { ulong id; if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) { - IUser user = null; + TUser user = null; //Verify this user was actually mentioned foreach (var userMention in mentionedUsers) { if (userMention.Id == id) { - if (channel.IsAttached) //Waiting this sync is safe because it's using a cache - user = channel.GetUserAsync(id).GetAwaiter().GetResult() as IUser; + user = channel?.GetCachedUser(id) as TUser; if (user == null) //User not found, fallback to basic mention info user = userMention; break; @@ -134,32 +55,28 @@ namespace Discord { ulong id; if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - { - /*var channel = guild.GetChannelAsync(id).GetAwaiter().GetResult(); - if (channel != null) - builder.Add(channel);*/ builder.Add(id); - } } return builder.ToImmutable(); } - internal static ImmutableArray GetRoleMentions(string text, IGuild guild) + internal static ImmutableArray GetRoleMentions(string text, IGuild guild) + where TRole : class, IRole { var matches = _roleRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); + var builder = ImmutableArray.CreateBuilder(matches.Count); foreach (var match in matches.OfType()) { ulong id; if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) { - var role = guild.GetRole(id); + var role = guild.GetRole(id) as TRole; if (role != null) builder.Add(role); } } return builder.ToImmutable(); } - + internal static string ResolveUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentions, UserMentionHandling mode) { if (mode == UserMentionHandling.Ignore) return text; @@ -218,16 +135,12 @@ namespace Discord case ChannelMentionHandling.Remove: return ""; case ChannelMentionHandling.Name: - if (guild != null && guild.IsAttached) //It's too expensive to do a channel lookup in REST mode - { - IGuildChannel channel = null; - channel = guild.GetChannel(id); - if (channel != null) - return $"#{channel.Name}"; - else - return $"#deleted-channel"; - } - break; + IGuildChannel channel = null; + channel = guild.GetCachedChannel(id); + if (channel != null) + return $"#{channel.Name}"; + else + return $"#deleted-channel"; } } return e.Value; @@ -236,7 +149,7 @@ namespace Discord internal static string ResolveRoleMentions(string text, IReadOnlyCollection mentions, RoleMentionHandling mode) { if (mode == RoleMentionHandling.Ignore) return text; - + return _roleRegex.Replace(text, new MatchEvaluator(e => { ulong id; @@ -268,7 +181,7 @@ namespace Discord internal static string ResolveEveryoneMentions(string text, EveryoneMentionHandling mode) { if (mode == EveryoneMentionHandling.Ignore) return text; - + switch (mode) { case EveryoneMentionHandling.Sanitize: diff --git a/src/Discord.Net.Utils/Paging/Page.cs b/src/Discord.Net.Utils/Paging/Page.cs new file mode 100644 index 000000000..996d0ac6a --- /dev/null +++ b/src/Discord.Net.Utils/Paging/Page.cs @@ -0,0 +1,22 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Discord +{ + internal class Page : IReadOnlyCollection + { + private readonly IReadOnlyCollection _items; + public int Index { get; } + + public Page(PageInfo info, IEnumerable source) + { + Index = info.Page; + _items = source.ToImmutableArray(); + } + + int IReadOnlyCollection.Count => _items.Count; + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); + } +} diff --git a/src/Discord.Net.Utils/Paging/PageInfo.cs b/src/Discord.Net.Utils/Paging/PageInfo.cs new file mode 100644 index 000000000..cb573f3cf --- /dev/null +++ b/src/Discord.Net.Utils/Paging/PageInfo.cs @@ -0,0 +1,20 @@ +namespace Discord +{ + internal class PageInfo + { + public int Page { get; set; } + public ulong? Position { get; set; } + public uint? Count { get; set; } + public int PageSize { get; set; } + public uint? Remaining { get; set; } + + internal PageInfo(ulong? pos, uint? count, int pageSize) + { + Page = 1; + Position = pos; + Count = count; + Remaining = count; + PageSize = pageSize; + } + } +} diff --git a/src/Discord.Net.Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Utils/Paging/PagedEnumerator.cs new file mode 100644 index 000000000..3f347440b --- /dev/null +++ b/src/Discord.Net.Utils/Paging/PagedEnumerator.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord +{ + internal class PagedAsyncEnumerable : IAsyncEnumerable> + { + public int PageSize { get; } + + private readonly ulong? _start; + private readonly uint? _count; + private readonly Func>> _getPage; + private readonly Action> _nextPage; + + public PagedAsyncEnumerable(int pageSize, Func>> getPage, Action> nextPage = null, + ulong? start = null, uint? count = null) + { + PageSize = pageSize; + _start = start; + _count = count; + + _getPage = getPage; + _nextPage = nextPage; + } + + public IAsyncEnumerator> GetEnumerator() => new Enumerator(this); + internal class Enumerator : IAsyncEnumerator> + { + private readonly PagedAsyncEnumerable _source; + private readonly PageInfo _info; + + public IReadOnlyCollection Current { get; private set; } + + public Enumerator(PagedAsyncEnumerable source) + { + _source = source; + _info = new PageInfo(source._start, source._count, source.PageSize); + } + + public async Task MoveNext(CancellationToken cancelToken) + { + var data = await _source._getPage(_info, cancelToken); + Current = new Page(_info, data); + + _info.Page++; + _info.Remaining -= (uint)Current.Count; + _info.PageSize = _info.Remaining != null ? (int)Math.Min(_info.Remaining.Value, (ulong)_source.PageSize) : _source.PageSize; + _source?._nextPage(_info, data); + + return _info.Remaining > 0; + } + + public void Dispose() { Current = null; } + } + } + + public static class PagedAsyncEnumerable + { + public static async Task> Flatten(this IAsyncEnumerable> source) + { + return (await source.ToArray().ConfigureAwait(false)).SelectMany(x => x); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/Entities/Permissions/Permissions.cs b/src/Discord.Net.Utils/Permissions.cs similarity index 91% rename from src/Discord.Net/Entities/Permissions/Permissions.cs rename to src/Discord.Net.Utils/Permissions.cs index d7cfe3191..390c142de 100644 --- a/src/Discord.Net/Entities/Permissions/Permissions.cs +++ b/src/Discord.Net.Utils/Permissions.cs @@ -5,7 +5,7 @@ namespace Discord internal static class Permissions { public const int MaxBits = 53; - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission bit) => GetValue(allow, deny, (byte)bit); @@ -86,16 +86,16 @@ namespace Discord [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); - public static ulong ResolveGuild(IGuildUser user) + public static ulong ResolveGuild(IGuild guild, IGuildUser user) { ulong resolvedPermissions = 0; - - if (user.Id == user.Guild.OwnerId) + + if (user.Id == guild.OwnerId) resolvedPermissions = GuildPermissions.All.RawValue; //Owners always have all permissions else { - foreach (var role in user.Roles) - resolvedPermissions |= role.Permissions.RawValue; + foreach (var roleId in user.RoleIds) + resolvedPermissions |= guild.GetRole(roleId)?.Permissions.RawValue ?? 0; if (GetValue(resolvedPermissions, GuildPermission.Administrator)) resolvedPermissions = GuildPermissions.All.RawValue; //Administrators always have all permissions } @@ -106,7 +106,7 @@ namespace Discord { return ResolveChannel(user, channel, ResolveGuild(user)); }*/ - public static ulong ResolveChannel(IGuildUser user, IGuildChannel channel, ulong guildPermissions) + public static ulong ResolveChannel(IGuild guild, IGuildUser user, IGuildChannel channel, ulong guildPermissions) { ulong resolvedPermissions = 0; @@ -119,13 +119,13 @@ namespace Discord resolvedPermissions = guildPermissions; OverwritePermissions? perms; - var roles = user.Roles; - if (roles.Count > 0) + var roleIds = user.RoleIds; + if (roleIds.Count > 0) { ulong deniedPermissions = 0UL, allowedPermissions = 0UL; - foreach (var role in roles) + foreach (var roleId in roleIds) { - perms = channel.GetPermissionOverwrite(role); + perms = channel.GetPermissionOverwrite(guild.GetRole(roleId)); if (perms != null) { deniedPermissions |= perms.Value.DenyValue; @@ -137,7 +137,7 @@ namespace Discord perms = channel.GetPermissionOverwrite(user); if (perms != null) resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; - + //TODO: C#7 Typeswitch candidate var textChannel = channel as ITextChannel; var voiceChannel = channel as IVoiceChannel; @@ -151,4 +151,4 @@ namespace Discord return resolvedPermissions; } } -} +} \ No newline at end of file diff --git a/src/Discord.Net/Utilities/Preconditions.cs b/src/Discord.Net.Utils/Preconditions.cs similarity index 99% rename from src/Discord.Net/Utilities/Preconditions.cs rename to src/Discord.Net.Utils/Preconditions.cs index 1bd8da7ac..14c9db24d 100644 --- a/src/Discord.Net/Utilities/Preconditions.cs +++ b/src/Discord.Net.Utils/Preconditions.cs @@ -1,5 +1,4 @@ -using Discord.API; -using System; +using System; namespace Discord { diff --git a/src/Discord.Net/API/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs similarity index 99% rename from src/Discord.Net/API/DiscordSocketApiClient.cs rename to src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs index 2d2579273..38124de63 100644 --- a/src/Discord.Net/API/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs @@ -4,7 +4,6 @@ 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; diff --git a/src/Discord.Net/API/DiscordVoiceAPIClient.cs b/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs similarity index 100% rename from src/Discord.Net/API/DiscordVoiceAPIClient.cs rename to src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs diff --git a/src/Discord.Net/API/Gateway/ExtendedGuild.cs b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs similarity index 100% rename from src/Discord.Net/API/Gateway/ExtendedGuild.cs rename to src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs diff --git a/src/Discord.Net/API/Gateway/GatewayOpCode.cs b/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GatewayOpCode.cs rename to src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs diff --git a/src/Discord.Net/API/Gateway/GuildBanEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildBanEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildEmojiUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildEmojiUpdateEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildMemberAddEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildMemberAddEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildMemberRemoveEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildMemberRemoveEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildMemberUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildMemberUpdateEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildMembersChunkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildMembersChunkEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildRoleCreateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildRoleCreateEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildRoleDeleteEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildRoleDeleteEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildRoleUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildRoleUpdateEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs diff --git a/src/Discord.Net/API/Gateway/GuildSyncEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/GuildSyncEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs diff --git a/src/Discord.Net/API/Gateway/HelloEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/HelloEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs diff --git a/src/Discord.Net/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs similarity index 100% rename from src/Discord.Net/API/Gateway/IdentifyParams.cs rename to src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs diff --git a/src/Discord.Net/API/Gateway/MessageDeleteBulkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/MessageDeleteBulkEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs diff --git a/src/Discord.Net/API/Gateway/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/ReadyEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs diff --git a/src/Discord.Net/API/Gateway/RecipientEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/RecipientEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs diff --git a/src/Discord.Net/API/Gateway/RequestMembersParams.cs b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs similarity index 100% rename from src/Discord.Net/API/Gateway/RequestMembersParams.cs rename to src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs diff --git a/src/Discord.Net/API/Gateway/ResumeParams.cs b/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs similarity index 100% rename from src/Discord.Net/API/Gateway/ResumeParams.cs rename to src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs diff --git a/src/Discord.Net/API/Gateway/ResumedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/ResumedEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs diff --git a/src/Discord.Net/API/Gateway/StatusUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs similarity index 100% rename from src/Discord.Net/API/Gateway/StatusUpdateParams.cs rename to src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs diff --git a/src/Discord.Net/API/Gateway/TypingStartEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/TypingStartEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs diff --git a/src/Discord.Net/API/Gateway/UpdateStatusParams.cs b/src/Discord.Net.WebSocket/API/Gateway/UpdateStatusParams.cs similarity index 100% rename from src/Discord.Net/API/Gateway/UpdateStatusParams.cs rename to src/Discord.Net.WebSocket/API/Gateway/UpdateStatusParams.cs diff --git a/src/Discord.Net/API/Gateway/VoiceServerUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs similarity index 100% rename from src/Discord.Net/API/Gateway/VoiceServerUpdateEvent.cs rename to src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs diff --git a/src/Discord.Net/API/Gateway/VoiceStateUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs similarity index 100% rename from src/Discord.Net/API/Gateway/VoiceStateUpdateParams.cs rename to src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs diff --git a/src/Discord.Net/API/Voice/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs similarity index 100% rename from src/Discord.Net/API/Voice/IdentifyParams.cs rename to src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs diff --git a/src/Discord.Net/API/Voice/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs similarity index 100% rename from src/Discord.Net/API/Voice/ReadyEvent.cs rename to src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs diff --git a/src/Discord.Net/API/Voice/SelectProtocolParams.cs b/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs similarity index 100% rename from src/Discord.Net/API/Voice/SelectProtocolParams.cs rename to src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs diff --git a/src/Discord.Net/API/Voice/SessionDescriptionEvent.cs b/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs similarity index 100% rename from src/Discord.Net/API/Voice/SessionDescriptionEvent.cs rename to src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs diff --git a/src/Discord.Net/API/Voice/SpeakingParams.cs b/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs similarity index 100% rename from src/Discord.Net/API/Voice/SpeakingParams.cs rename to src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs diff --git a/src/Discord.Net/API/Voice/UdpProtocolInfo.cs b/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs similarity index 100% rename from src/Discord.Net/API/Voice/UdpProtocolInfo.cs rename to src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs diff --git a/src/Discord.Net/API/Voice/VoiceOpCode.cs b/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs similarity index 100% rename from src/Discord.Net/API/Voice/VoiceOpCode.cs rename to src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs diff --git a/src/Discord.Net/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs similarity index 100% rename from src/Discord.Net/Audio/AudioClient.cs rename to src/Discord.Net.WebSocket/Audio/AudioClient.cs diff --git a/src/Discord.Net/Audio/AudioMode.cs b/src/Discord.Net.WebSocket/Audio/AudioMode.cs similarity index 100% rename from src/Discord.Net/Audio/AudioMode.cs rename to src/Discord.Net.WebSocket/Audio/AudioMode.cs diff --git a/src/Discord.Net/Audio/Opus/OpusConverter.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusConverter.cs similarity index 100% rename from src/Discord.Net/Audio/Opus/OpusConverter.cs rename to src/Discord.Net.WebSocket/Audio/Opus/OpusConverter.cs diff --git a/src/Discord.Net/Audio/Opus/OpusCtl.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusCtl.cs similarity index 100% rename from src/Discord.Net/Audio/Opus/OpusCtl.cs rename to src/Discord.Net.WebSocket/Audio/Opus/OpusCtl.cs diff --git a/src/Discord.Net/Audio/Opus/OpusDecoder.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs similarity index 100% rename from src/Discord.Net/Audio/Opus/OpusDecoder.cs rename to src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs diff --git a/src/Discord.Net/Audio/Opus/OpusEncoder.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs similarity index 100% rename from src/Discord.Net/Audio/Opus/OpusEncoder.cs rename to src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs diff --git a/src/Discord.Net/Audio/Opus/OpusError.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusError.cs similarity index 100% rename from src/Discord.Net/Audio/Opus/OpusError.cs rename to src/Discord.Net.WebSocket/Audio/Opus/OpusError.cs diff --git a/src/Discord.Net/Audio/Sodium/SecretBox.cs b/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs similarity index 100% rename from src/Discord.Net/Audio/Sodium/SecretBox.cs rename to src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs diff --git a/src/Discord.Net/Audio/Streams/OpusDecodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs similarity index 100% rename from src/Discord.Net/Audio/Streams/OpusDecodeStream.cs rename to src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs diff --git a/src/Discord.Net/Audio/Streams/OpusEncodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs similarity index 100% rename from src/Discord.Net/Audio/Streams/OpusEncodeStream.cs rename to src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs diff --git a/src/Discord.Net/Audio/Streams/RTPReadStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs similarity index 100% rename from src/Discord.Net/Audio/Streams/RTPReadStream.cs rename to src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs diff --git a/src/Discord.Net/Audio/Streams/RTPWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs similarity index 100% rename from src/Discord.Net/Audio/Streams/RTPWriteStream.cs rename to src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs diff --git a/src/Discord.Net/WebSocket/DataStore.cs b/src/Discord.Net.WebSocket/DataStore.cs similarity index 100% rename from src/Discord.Net/WebSocket/DataStore.cs rename to src/Discord.Net.WebSocket/DataStore.cs diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj new file mode 100644 index 000000000..da99d6e1b --- /dev/null +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d + Discord.Net.WebSocket + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/src/Discord.Net/WebSocket/DiscordSocketClient.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs similarity index 100% rename from src/Discord.Net/WebSocket/DiscordSocketClient.Events.cs rename to src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs diff --git a/src/Discord.Net/WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs similarity index 99% rename from src/Discord.Net/WebSocket/DiscordSocketClient.cs rename to src/Discord.Net.WebSocket/DiscordSocketClient.cs index a77110c66..c90e9d7c6 100644 --- a/src/Discord.Net/WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -393,7 +393,7 @@ namespace Discord.WebSocket case ChannelType.Group: { var channel = new SocketGroupChannel(this, model); - channel.UpdateUsers(model.Recipients.Value, UpdateSource.Creation, dataStore); + channel.UpdateUsers(model.Recipients.Value, dataStore); dataStore.AddChannel(channel); return channel; } diff --git a/src/Discord.Net/WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs similarity index 100% rename from src/Discord.Net/WebSocket/DiscordSocketConfig.cs rename to src/Discord.Net.WebSocket/DiscordSocketConfig.cs diff --git a/src/Discord.Net/WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs similarity index 93% rename from src/Discord.Net/WebSocket/Entities/Channels/SocketDMChannel.cs rename to src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 958c71bba..c19508856 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -1,5 +1,4 @@ -using Discord.Rest; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; using MessageModel = Discord.API.Message; @@ -7,10 +6,8 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { - internal class SocketDMChannel : DMChannel, IDMChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel + internal class DMChannel : IDMChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel { - internal override bool IsAttached => true; - private readonly MessageManager _messages; public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs similarity index 93% rename from src/Discord.Net/WebSocket/Entities/Channels/SocketGroupChannel.cs rename to src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 8d456bb55..cbe6d9c8f 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -11,7 +11,7 @@ using VoiceStateModel = Discord.API.VoiceState; namespace Discord.WebSocket { - internal class SocketGroupChannel : GroupChannel, IGroupChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel + internal class SocketGroupChannel : IGroupChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel { internal override bool IsAttached => true; @@ -32,14 +32,14 @@ namespace Discord.WebSocket _messages = new MessageManager(Discord, this); _voiceStates = new ConcurrentDictionary(1, 5); } - public override void Update(Model model, UpdateSource source) + public override void Update(Model model) { if (source == UpdateSource.Rest && IsAttached) return; base.Update(model, source); } - internal void UpdateUsers(UserModel[] models, UpdateSource source, DataStore dataStore) + internal void UpdateUsers(UserModel[] models, DataStore dataStore) { var users = new ConcurrentDictionary(1, models.Length); for (int i = 0; i < models.Length; i++) @@ -49,7 +49,7 @@ namespace Discord.WebSocket } _users = users; } - internal override void UpdateUsers(UserModel[] models, UpdateSource source) + internal override void UpdateUsers(UserModel[] models) => UpdateUsers(models, source, Discord.DataStore); public SocketGroupUser AddUser(UserModel model, DataStore dataStore) diff --git a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs similarity index 96% rename from src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs rename to src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 6b5777570..613a79064 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal abstract class GuildChannel : SnowflakeEntity, IGuildChannel + internal abstract class SocketGuildChannel : ISnowflakeEntity, IGuildChannel { private List _overwrites; //TODO: Is maintaining a list here too expensive? Is this threadsafe? @@ -26,9 +26,9 @@ namespace Discord.Rest { Guild = guild; - Update(model, UpdateSource.Creation); + Update(model); } - public virtual void Update(Model model, UpdateSource source) + public virtual void Update(Model model) { if (source == UpdateSource.Rest && IsAttached) return; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Channels/SocketTextChannel.cs rename to src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs diff --git a/src/Discord.Net/WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Channels/SocketVoiceChannel.cs rename to src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs similarity index 98% rename from src/Discord.Net/WebSocket/Entities/Guilds/SocketGuild.cs rename to src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index ea0eb07a9..7ddab5ad6 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -60,10 +60,10 @@ namespace Discord.WebSocket _audioLock = new SemaphoreSlim(1, 1); _syncPromise = new TaskCompletionSource(); _downloaderPromise = new TaskCompletionSource(); - Update(model, UpdateSource.Creation, dataStore); + Update(model, dataStore); } - public void Update(ExtendedModel model, UpdateSource source, DataStore dataStore) + public void Update(ExtendedModel model, DataStore dataStore) { if (source == UpdateSource.Rest && IsAttached) return; @@ -117,7 +117,7 @@ namespace Discord.WebSocket } _voiceStates = voiceStates; } - public void Update(GuildSyncModel model, UpdateSource source, DataStore dataStore) + public void Update(GuildSyncModel model, DataStore dataStore) { if (source == UpdateSource.Rest && IsAttached) return; @@ -136,7 +136,7 @@ namespace Discord.WebSocket _members = members; } - public void Update(EmojiUpdateModel model, UpdateSource source) + public void Update(EmojiUpdateModel model) { if (source == UpdateSource.Rest && IsAttached) return; diff --git a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs similarity index 90% rename from src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs rename to src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs index 135c0f3f3..bd65a3cc8 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs @@ -1,5 +1,4 @@ using Discord.API.Rest; -using Discord.Rest; using System; using System.Diagnostics; using System.Threading.Tasks; @@ -8,7 +7,7 @@ using Model = Discord.API.Integration; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class GuildIntegration : Entity, IGuildIntegration + internal class GuildIntegration : IEntity, IGuildIntegration { private long _syncedAtTicks; @@ -31,13 +30,11 @@ namespace Discord.Rest : base(model.Id) { Guild = guild; - Update(model, UpdateSource.Creation); + Update(model); } - public void Update(Model model, UpdateSource source) + public void Update(Model model) { - if (source == UpdateSource.Rest && IsAttached) return; - Name = model.Name; Type = model.Type; IsEnabled = model.Enabled; @@ -49,7 +46,7 @@ namespace Discord.Rest Role = Guild.GetRole(model.RoleId); User = new User(model.User); } - + public async Task DeleteAsync() { await Discord.ApiClient.DeleteGuildIntegrationAsync(Guild.Id, Id).ConfigureAwait(false); diff --git a/src/Discord.Net/WebSocket/Entities/Messages/ISocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs similarity index 82% rename from src/Discord.Net/WebSocket/Entities/Messages/ISocketMessage.cs rename to src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs index 818d62ec0..c4f112b77 100644 --- a/src/Discord.Net/WebSocket/Entities/Messages/ISocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs @@ -7,7 +7,7 @@ namespace Discord.WebSocket DiscordSocketClient Discord { get; } new ISocketMessageChannel Channel { get; } - void Update(Model model, UpdateSource source); + void Update(Model model); ISocketMessage Clone(); } } diff --git a/src/Discord.Net/WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Messages/SocketSystemMessage.cs rename to src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs diff --git a/src/Discord.Net/WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Messages/SocketUserMessage.cs rename to src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs diff --git a/src/Discord.Net/WebSocket/Entities/Users/ISocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Users/ISocketUser.cs rename to src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs diff --git a/src/Discord.Net/WebSocket/Entities/Users/Presence.cs b/src/Discord.Net.WebSocket/Entities/Users/Presence.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Users/Presence.cs rename to src/Discord.Net.WebSocket/Entities/Users/Presence.cs diff --git a/src/Discord.Net/WebSocket/Entities/Users/SocketDMUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs similarity index 95% rename from src/Discord.Net/WebSocket/Entities/Users/SocketDMUser.cs rename to src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs index b996ce94c..d68a395f6 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/SocketDMUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs @@ -32,7 +32,7 @@ namespace Discord.WebSocket User = user; } - public void Update(PresenceModel model, UpdateSource source) + public void Update(PresenceModel model) { User.Update(model, source); } diff --git a/src/Discord.Net/WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs similarity index 91% rename from src/Discord.Net/WebSocket/Entities/Users/SocketGlobalUser.cs rename to src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 3835dcc46..53180ebfc 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -38,12 +38,12 @@ namespace Discord.WebSocket } } - public override void Update(Model model, UpdateSource source) + public override void Update(Model model) { lock (this) base.Update(model, source); } - public void Update(PresenceModel model, UpdateSource source) + public void Update(PresenceModel model) { //Race conditions are okay here. Multiple shards racing already cant guarantee presence in order. diff --git a/src/Discord.Net/WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Users/SocketGroupUser.cs rename to src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs diff --git a/src/Discord.Net/WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs similarity index 96% rename from src/Discord.Net/WebSocket/Entities/Users/SocketGuildUser.cs rename to src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index ab01956cc..3b38eb213 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -35,7 +35,7 @@ namespace Discord.WebSocket { } - public override void Update(PresenceModel model, UpdateSource source) + public override void Update(PresenceModel model) { base.Update(model, source); diff --git a/src/Discord.Net/WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Users/SocketSelfUser.cs rename to src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs diff --git a/src/Discord.Net/WebSocket/Entities/Users/VoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/VoiceState.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Users/VoiceState.cs rename to src/Discord.Net.WebSocket/Entities/Users/VoiceState.cs diff --git a/src/Discord.Net/WebSocket/Entities/Channels/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Utilities/MessageCache.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Channels/MessageCache.cs rename to src/Discord.Net.WebSocket/Entities/Utilities/MessageCache.cs diff --git a/src/Discord.Net/WebSocket/Entities/Channels/MessageManager.cs b/src/Discord.Net.WebSocket/Entities/Utilities/MessageManager.cs similarity index 100% rename from src/Discord.Net/WebSocket/Entities/Channels/MessageManager.cs rename to src/Discord.Net.WebSocket/Entities/Utilities/MessageManager.cs diff --git a/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs b/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..2aae053d2 --- /dev/null +++ b/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Discord.Net.WebSocket")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d")] diff --git a/src/Discord.Net.WebSocket/project.json b/src/Discord.Net.WebSocket/project.json new file mode 100644 index 000000000..c52db6ba9 --- /dev/null +++ b/src/Discord.Net.WebSocket/project.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0-*", + + "buildOptions": { + "compile": { + "include": [ "../Discord.Net.Entities/**.cs", "../Discord.Net.Utils/**.cs" ] + }, + "define": [ "WEBSOCKET" ] + }, + + "dependencies": { + "NETStandard.Library": "1.6.0" + }, + + "frameworks": { + "netstandard1.6": { + "imports": "dnxcore50" + } + } +} diff --git a/src/Discord.Net/API/Rest/CreateChannelInviteParams.cs b/src/Discord.Net/API/Rest/CreateChannelInviteParams.cs deleted file mode 100644 index 0899a7e68..000000000 --- a/src/Discord.Net/API/Rest/CreateChannelInviteParams.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class CreateChannelInviteParams - { - [JsonProperty("max_age")] - internal Optional _maxAge { get; set; } - public int MaxAge { set { _maxAge = value; } } - - [JsonProperty("max_uses")] - internal Optional _maxUses { get; set; } - public int MaxUses { set { _maxUses = value; } } - - [JsonProperty("temporary")] - internal Optional _temporary { get; set; } - public bool Temporary { set { _temporary = value; } } - } -} diff --git a/src/Discord.Net/API/Rest/CreateGuildChannelParams.cs b/src/Discord.Net/API/Rest/CreateGuildChannelParams.cs deleted file mode 100644 index fe1428cf8..000000000 --- a/src/Discord.Net/API/Rest/CreateGuildChannelParams.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class CreateGuildChannelParams - { - [JsonProperty("name")] - internal string _name { get; set; } - public string Name { set { _name = value; } } - - [JsonProperty("type")] - internal ChannelType _type { get; set; } - public ChannelType Type { set { _type = value; } } - - [JsonProperty("bitrate")] - internal Optional _bitrate { get; set; } - public int Bitrate { set { _bitrate = value; } } - } -} diff --git a/src/Discord.Net/API/Rest/CreateMessageParams.cs b/src/Discord.Net/API/Rest/CreateMessageParams.cs deleted file mode 100644 index 1a586c4cf..000000000 --- a/src/Discord.Net/API/Rest/CreateMessageParams.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class CreateMessageParams - { - [JsonProperty("content")] - internal string _content { get; set; } - public string Content { set { _content = value; } } - - [JsonProperty("nonce")] - internal Optional _nonce { get; set; } - public string Nonce { set { _nonce = value; } } - - [JsonProperty("tts")] - internal Optional _tts { get; set; } - public bool IsTTS { set { _tts = value; } } - } -} diff --git a/src/Discord.Net/API/Rest/DeleteMessagesParams.cs b/src/Discord.Net/API/Rest/DeleteMessagesParams.cs deleted file mode 100644 index 89e279f13..000000000 --- a/src/Discord.Net/API/Rest/DeleteMessagesParams.cs +++ /dev/null @@ -1,16 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Linq; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class DeleteMessagesParams - { - [JsonProperty("messages")] - internal ulong[] _messages { get; set; } - public IEnumerable MessageIds { set { _messages = value.ToArray(); } } - public IEnumerable Messages { set { _messages = value.Select(x => x.Id).ToArray(); } } - } -} diff --git a/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs b/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs deleted file mode 100644 index 68660ab01..000000000 --- a/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs +++ /dev/null @@ -1,14 +0,0 @@ -#pragma warning disable CS1591 -namespace Discord.API.Rest -{ - public class GetChannelMessagesParams - { - public int Limit { internal get; set; } = DiscordConfig.MaxMessagesPerBatch; - - public Direction RelativeDirection { internal get; set; } = Direction.Before; - - internal Optional _relativeMessageId { get; set; } - public ulong RelativeMessageId { set { _relativeMessageId = value; } } - public IMessage RelativeMessage { set { _relativeMessageId = value.Id; } } - } -} diff --git a/src/Discord.Net/API/Rest/GetGuildMembersParams.cs b/src/Discord.Net/API/Rest/GetGuildMembersParams.cs deleted file mode 100644 index cefca801e..000000000 --- a/src/Discord.Net/API/Rest/GetGuildMembersParams.cs +++ /dev/null @@ -1,12 +0,0 @@ -#pragma warning disable CS1591 -namespace Discord.API.Rest -{ - public class GetGuildMembersParams - { - internal Optional _limit { get; set; } - public int Limit { set { _limit = value; } } - - internal Optional _afterUserId { get; set; } - public ulong AfterUserId { set { _afterUserId = value; } } - } -} diff --git a/src/Discord.Net/API/Rest/ModifyGuildEmbedParams.cs b/src/Discord.Net/API/Rest/ModifyGuildEmbedParams.cs deleted file mode 100644 index 84a7f28ca..000000000 --- a/src/Discord.Net/API/Rest/ModifyGuildEmbedParams.cs +++ /dev/null @@ -1,18 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class ModifyGuildEmbedParams - { - [JsonProperty("enabled")] - internal Optional _enabled { get; set; } - public bool Enabled { set { _enabled = value; } } - - [JsonProperty("channel")] - internal Optional _channelId { get; set; } - public ulong? ChannelId { set { _channelId = value; } } - public IVoiceChannel Channel { set { _channelId = value != null ? value.Id : (ulong?)null; } } - } -} diff --git a/src/Discord.Net/API/Rest/ModifyGuildIntegrationParams.cs b/src/Discord.Net/API/Rest/ModifyGuildIntegrationParams.cs deleted file mode 100644 index 9b280f8ae..000000000 --- a/src/Discord.Net/API/Rest/ModifyGuildIntegrationParams.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class ModifyGuildIntegrationParams - { - [JsonProperty("expire_behavior")] - internal Optional _expireBehavior { get; set; } - public int ExpireBehavior { set { _expireBehavior = value; } } - - [JsonProperty("expire_grace_period")] - internal Optional _expireGracePeriod { get; set; } - public int ExpireGracePeriod { set { _expireGracePeriod = value; } } - - [JsonProperty("enable_emoticons")] - internal Optional _enableEmoticons { get; set; } - public bool EnableEmoticons { set { _enableEmoticons = value; } } - } -} diff --git a/src/Discord.Net/API/Rest/ModifyGuildMemberParams.cs b/src/Discord.Net/API/Rest/ModifyGuildMemberParams.cs deleted file mode 100644 index 4377efca2..000000000 --- a/src/Discord.Net/API/Rest/ModifyGuildMemberParams.cs +++ /dev/null @@ -1,33 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Linq; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class ModifyGuildMemberParams - { - [JsonProperty("mute")] - internal Optional _mute { get; set; } - public bool Mute { set { _mute = value; } } - - [JsonProperty("deaf")] - internal Optional _deaf { get; set; } - public bool Deaf { set { _deaf = value; } } - - [JsonProperty("nick")] - internal Optional _nickname { get; set; } - public string Nickname { set { _nickname = value; } } - - [JsonProperty("roles")] - internal Optional _roleIds { get; set; } - public IEnumerable RoleIds { set { _roleIds = value.ToArray(); } } - public IEnumerable Roles { set { _roleIds = value.Select(x => x.Id).ToArray(); } } - - [JsonProperty("channel_id")] - internal Optional _channelId { get; set; } - public ulong VoiceChannelId { set { _channelId = value; } } - public IVoiceChannel VoiceChannel { set { _channelId = value.Id; } } - } -} diff --git a/src/Discord.Net/API/Rest/ModifyGuildParams.cs b/src/Discord.Net/API/Rest/ModifyGuildParams.cs deleted file mode 100644 index e7e8219bd..000000000 --- a/src/Discord.Net/API/Rest/ModifyGuildParams.cs +++ /dev/null @@ -1,52 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; -using System.IO; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class ModifyGuildParams - { - [JsonProperty("username")] - internal Optional _username { get; set; } - public string Username { set { _username = value; } } - - [JsonProperty("name")] - internal Optional _name { get; set; } - public string Name { set { _name = value; } } - - [JsonProperty("region")] - internal Optional _region { get; set; } - public IVoiceRegion Region { set { _region = Optional.Create(value); } } - - [JsonProperty("verification_level")] - internal Optional _verificationLevel { get; set; } - public VerificationLevel VerificationLevel { set { _verificationLevel = value; } } - - [JsonProperty("default_message_notifications")] - internal Optional _defaultMessageNotifications { get; set; } - public DefaultMessageNotifications DefaultMessageNotifications { set { _defaultMessageNotifications = value; } } - - [JsonProperty("afk_timeout")] - internal Optional _afkTimeout { get; set; } - public int AFKTimeout { set { _afkTimeout = value; } } - - [JsonProperty("icon")] - internal Optional _icon { get; set; } - public Stream Icon { set { _icon = value != null ? new Image(value) : (Image?)null; } } - - [JsonProperty("splash")] - internal Optional _splash { get; set; } - public Stream Splash { set { _splash = value != null ? new Image(value) : (Image?)null; } } - - [JsonProperty("afk_channel_id")] - internal Optional _afkChannelId { get; set; } - public ulong? AFKChannelId { set { _afkChannelId = value; } } - public IVoiceChannel AFKChannel { set { _afkChannelId = value?.Id; } } - - [JsonProperty("owner_id")] - internal Optional _ownerId { get; set; } - public ulong OwnerId { set { _ownerId = value; } } - public IGuildUser Owner { set { _ownerId = value.Id; } } - } -} diff --git a/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs b/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs deleted file mode 100644 index a9e7eeb3d..000000000 --- a/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs +++ /dev/null @@ -1,29 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - public class ModifyGuildRoleParams - { - [JsonProperty("name")] - internal Optional _name { get; set; } - public string Name { set { _name = value; } } - - [JsonProperty("permissions")] - internal Optional _permissions { get; set; } - public ulong Permissions { set { _permissions = value; } } - - [JsonProperty("position")] - internal Optional _position { get; set; } - public int Position { set { _position = value; } } - - [JsonProperty("color")] - internal Optional _color { get; set; } - public uint Color { set { _color = value; } } - - [JsonProperty("hoist")] - internal Optional _hoist { get; set; } - public bool Hoist { set { _hoist = value; } } - } -} diff --git a/src/Discord.Net/API/Rest/ModifyPresenceParams.cs b/src/Discord.Net/API/Rest/ModifyPresenceParams.cs deleted file mode 100644 index 00d64bfd6..000000000 --- a/src/Discord.Net/API/Rest/ModifyPresenceParams.cs +++ /dev/null @@ -1,12 +0,0 @@ -#pragma warning disable CS1591 -namespace Discord.API.Rest -{ - public class ModifyPresenceParams - { - internal Optional _status { get; set; } - public UserStatus Status { set { _status = value; } } - - internal Optional _game { get; set; } - public Discord.Game Game { set { _game = value; } } - } -} diff --git a/src/Discord.Net/API/Rest/UploadFileParams.cs b/src/Discord.Net/API/Rest/UploadFileParams.cs deleted file mode 100644 index b454068d7..000000000 --- a/src/Discord.Net/API/Rest/UploadFileParams.cs +++ /dev/null @@ -1,42 +0,0 @@ -#pragma warning disable CS1591 -using Discord.Net.Rest; -using System.Collections.Generic; -using System.IO; - -namespace Discord.API.Rest -{ - public class UploadFileParams - { - public Stream File { internal get; set; } - - internal Optional _filename { get; set; } - public string Filename { set { _filename = value; } } - - internal Optional _content { get; set; } - public string Content { set { _content = value; } } - - internal Optional _nonce { get; set; } - public string Nonce { set { _nonce = value; } } - - internal Optional _isTTS { get; set; } - public bool IsTTS { set { _isTTS = value; } } - - public UploadFileParams(Stream file) - { - File = file; - } - - internal IReadOnlyDictionary ToDictionary() - { - var d = new Dictionary(); - d["file"] = new MultipartFile(File, _filename.GetValueOrDefault("unknown.dat")); - if (_content.IsSpecified) - d["content"] = _content.Value; - if (_isTTS.IsSpecified) - d["tts"] = _isTTS.Value.ToString(); - if (_nonce.IsSpecified) - d["nonce"] = _nonce.Value; - return d; - } - } -} diff --git a/src/Discord.Net/Discord.Net.xproj b/src/Discord.Net/Discord.Net.xproj index 6759e09b4..079338b62 100644 --- a/src/Discord.Net/Discord.Net.xproj +++ b/src/Discord.Net/Discord.Net.xproj @@ -6,11 +6,11 @@ - 91e9e7bd-75c9-4e98-84aa-2c271922e5c2 - Discord + 496db20a-a455-4d01-b6bc-90fe6d7c6b81 + Discord.Net .\obj .\bin\ - v4.5.2 + v4.6.1 2.0 diff --git a/src/Discord.Net/Entities/Channels/IGroupChannel.cs b/src/Discord.Net/Entities/Channels/IGroupChannel.cs deleted file mode 100644 index 6b71f76b6..000000000 --- a/src/Discord.Net/Entities/Channels/IGroupChannel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; - -namespace Discord -{ - public interface IGroupChannel : IMessageChannel, IPrivateChannel - { - /// Adds a user to this group. - Task AddUserAsync(IUser user); - - /// Leaves this group. - Task LeaveAsync(); - } -} \ No newline at end of file diff --git a/src/Discord.Net/Entities/Guilds/Ban.cs b/src/Discord.Net/Entities/Guilds/Ban.cs deleted file mode 100644 index 5d9d3df07..000000000 --- a/src/Discord.Net/Entities/Guilds/Ban.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Diagnostics; - -namespace Discord -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public struct Ban - { - public IUser User { get; } - public string Reason { get; } - - public Ban(IUser user, string reason) - { - User = user; - Reason = reason; - } - - public override string ToString() => User.ToString(); - private string DebuggerDisplay => $"{User}: {Reason}"; - } -} diff --git a/src/Discord.Net/Entities/Guilds/Emoji.cs b/src/Discord.Net/Entities/Guilds/Emoji.cs deleted file mode 100644 index 19ee306b0..000000000 --- a/src/Discord.Net/Entities/Guilds/Emoji.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Immutable; -using System.Diagnostics; -using Model = Discord.API.Emoji; - -namespace Discord -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public struct Emoji - { - public ulong Id { get; } - public string Name { get; } - public bool IsManaged { get; } - public bool RequireColons { get; } - public IImmutableList RoleIds { get; } - - public Emoji(Model model) - { - Id = model.Id; - Name = model.Name; - IsManaged = model.Managed; - RequireColons = model.RequireColons; - RoleIds = ImmutableArray.Create(model.Roles); - } - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; - } -} diff --git a/src/Discord.Net/Entities/IEntity.cs b/src/Discord.Net/Entities/IEntity.cs deleted file mode 100644 index d6d97626d..000000000 --- a/src/Discord.Net/Entities/IEntity.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Discord -{ - public interface IEntity - where TId : IEquatable - { - /// Gets the unique identifier for this object. - TId Id { get; } - - //TODO: What do we do when an object is destroyed due to reconnect? This summary isn't correct. - /// Returns true if this object is getting live updates from the DiscordClient. - bool IsAttached { get;} - } -} diff --git a/src/Discord.Net/Entities/ISnowflakeEntity.cs b/src/Discord.Net/Entities/ISnowflakeEntity.cs deleted file mode 100644 index 60623425c..000000000 --- a/src/Discord.Net/Entities/ISnowflakeEntity.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Discord -{ - public interface ISnowflakeEntity : IEntity - { - /// Gets when this object was created. - DateTimeOffset CreatedAt { get; } - } -} diff --git a/src/Discord.Net/Entities/Messages/ISystemMessage.cs b/src/Discord.Net/Entities/Messages/ISystemMessage.cs deleted file mode 100644 index d2e23d147..000000000 --- a/src/Discord.Net/Entities/Messages/ISystemMessage.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Discord -{ - public interface ISystemMessage : IMessage - { - /// Gets the type of this system message. - MessageType Type { get; } - } -} diff --git a/src/Discord.Net/Entities/UpdateSource.cs b/src/Discord.Net/Entities/UpdateSource.cs deleted file mode 100644 index 6c56416e7..000000000 --- a/src/Discord.Net/Entities/UpdateSource.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Discord -{ - internal enum UpdateSource - { - Creation, - Rest, - WebSocket - } -} diff --git a/src/Discord.Net/Entities/Users/IGroupUser.cs b/src/Discord.Net/Entities/Users/IGroupUser.cs deleted file mode 100644 index 8ed53616c..000000000 --- a/src/Discord.Net/Entities/Users/IGroupUser.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; - -namespace Discord -{ - public interface IGroupUser : IUser - { - /// Kicks this user from this group. - Task KickAsync(); - - /// Returns a private message channel to this user, creating one if it does not already exist. - Task CreateDMChannelAsync(); - } -} diff --git a/src/Discord.Net/Logging/ILogManager.cs b/src/Discord.Net/Logging/ILogManager.cs deleted file mode 100644 index b244419b9..000000000 --- a/src/Discord.Net/Logging/ILogManager.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Discord.Logging -{ - public interface ILogManager - { - LogSeverity Level { get; } - - Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null); - Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null); - Task LogAsync(LogSeverity severity, string source, Exception ex); - - Task ErrorAsync(string source, string message, Exception ex = null); - Task ErrorAsync(string source, FormattableString message, Exception ex = null); - Task ErrorAsync(string source, Exception ex); - - Task WarningAsync(string source, string message, Exception ex = null); - Task WarningAsync(string source, FormattableString message, Exception ex = null); - Task WarningAsync(string source, Exception ex); - - Task InfoAsync(string source, string message, Exception ex = null); - Task InfoAsync(string source, FormattableString message, Exception ex = null); - Task InfoAsync(string source, Exception ex); - - Task VerboseAsync(string source, string message, Exception ex = null); - Task VerboseAsync(string source, FormattableString message, Exception ex = null); - Task VerboseAsync(string source, Exception ex); - - Task DebugAsync(string source, string message, Exception ex = null); - Task DebugAsync(string source, FormattableString message, Exception ex = null); - Task DebugAsync(string source, Exception ex); - - ILogger CreateLogger(string name); - } -} diff --git a/src/Discord.Net/Logging/ILogger.cs b/src/Discord.Net/Logging/ILogger.cs deleted file mode 100644 index 207c03dc7..000000000 --- a/src/Discord.Net/Logging/ILogger.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Discord.Logging -{ - public interface ILogger - { - LogSeverity Level { get; } - - Task LogAsync(LogSeverity severity, string message, Exception exception = null); - Task LogAsync(LogSeverity severity, FormattableString message, Exception exception = null); - Task LogAsync(LogSeverity severity, Exception exception); - - Task ErrorAsync(string message, Exception exception = null); - Task ErrorAsync(FormattableString message, Exception exception = null); - Task ErrorAsync(Exception exception); - - Task WarningAsync(string message, Exception exception = null); - Task WarningAsync(FormattableString message, Exception exception = null); - Task WarningAsync(Exception exception); - - Task InfoAsync(string message, Exception exception = null); - Task InfoAsync(FormattableString message, Exception exception = null); - Task InfoAsync(Exception exception); - - Task VerboseAsync(string message, Exception exception = null); - Task VerboseAsync(FormattableString message, Exception exception = null); - Task VerboseAsync(Exception exception); - - Task DebugAsync(string message, Exception exception = null); - Task DebugAsync(FormattableString message, Exception exception = null); - Task DebugAsync(Exception exception); - } -} diff --git a/src/Discord.Net/Properties/AssemblyInfo.cs b/src/Discord.Net/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e23324a59 --- /dev/null +++ b/src/Discord.Net/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Discord.Net")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("496db20a-a455-4d01-b6bc-90fe6d7c6b81")] diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs deleted file mode 100644 index 6097a7f00..000000000 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Discord.API.Rest; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Model = Discord.API.Channel; -using MessageModel = Discord.API.Message; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class DMChannel : SnowflakeEntity, IDMChannel - { - public override DiscordRestClient Discord { get; } - public IUser Recipient { get; private set; } - - public virtual IReadOnlyCollection CachedMessages => ImmutableArray.Create(); - IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); - - public DMChannel(DiscordRestClient discord, IUser recipient, Model model) - : base(model.Id) - { - Discord = discord; - Recipient = recipient; - - Update(model, UpdateSource.Creation); - } - public void Update(Model model, UpdateSource source) - { - if (/*source == UpdateSource.Rest && */IsAttached) return; - - (Recipient as User).Update(model.Recipients.Value[0], source); - } - - public async Task UpdateAsync() - { - if (IsAttached) throw new NotSupportedException(); - - var model = await Discord.ApiClient.GetChannelAsync(Id).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task CloseAsync() - { - await Discord.ApiClient.DeleteChannelAsync(Id).ConfigureAwait(false); - } - - public virtual async Task GetUserAsync(ulong id) - { - var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); - if (id == Recipient.Id) - return Recipient; - else if (id == currentUser.Id) - return currentUser; - else - return null; - } - public virtual async Task> GetUsersAsync() - { - var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); - return ImmutableArray.Create(currentUser, Recipient); - } - - public async Task SendMessageAsync(string text, bool isTTS) - { - var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - public async Task SendFileAsync(string filePath, string text, bool isTTS) - { - string filename = Path.GetFileName(filePath); - using (var file = File.OpenRead(filePath)) - { - var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - } - public async Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - { - var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - public virtual async Task GetMessageAsync(ulong id) - { - var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); - if (model != null) - return CreateIncomingMessage(model); - return null; - } - public virtual async Task> GetMessagesAsync(int limit) - { - var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - public virtual async Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - { - var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; - var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - public async Task DeleteMessagesAsync(IEnumerable messages) - { - await Discord.ApiClient.DeleteDMMessagesAsync(Id, new DeleteMessagesParams { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); - } - public async Task> GetPinnedMessagesAsync() - { - var models = await Discord.ApiClient.GetPinsAsync(Id); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - - public async Task TriggerTypingAsync() - { - await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); - } - - private UserMessage CreateOutgoingMessage(MessageModel model) - { - return new UserMessage(this, new User(model.Author.Value), model); - } - private Message CreateIncomingMessage(MessageModel model) - { - if (model.Type == MessageType.Default) - return new UserMessage(this, new User(model.Author.Value), model); - else - return new SystemMessage(this, new User(model.Author.Value), model); - } - - public override string ToString() => '@' + Recipient.ToString(); - private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; - - IMessage IMessageChannel.GetCachedMessage(ulong id) => null; - } -} diff --git a/src/Discord.Net/Rest/Entities/Channels/GroupChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GroupChannel.cs deleted file mode 100644 index 3ed544087..000000000 --- a/src/Discord.Net/Rest/Entities/Channels/GroupChannel.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Discord.API.Rest; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Model = Discord.API.Channel; -using MessageModel = Discord.API.Message; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class GroupChannel : SnowflakeEntity, IGroupChannel - { - protected ConcurrentDictionary _users; - private string _iconId; - - public override DiscordRestClient Discord { get; } - public string Name { get; private set; } - - public IReadOnlyCollection Recipients => _users.ToReadOnlyCollection(); - public virtual IReadOnlyCollection CachedMessages => ImmutableArray.Create(); - public string IconUrl => API.CDN.GetChannelIconUrl(Id, _iconId); - - public GroupChannel(DiscordRestClient discord, Model model) - : base(model.Id) - { - Discord = discord; - - Update(model, UpdateSource.Creation); - } - public virtual void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - if (model.Name.IsSpecified) - Name = model.Name.Value; - if (model.Icon.IsSpecified) - _iconId = model.Icon.Value; - - if (source != UpdateSource.Creation && model.Recipients.IsSpecified) - UpdateUsers(model.Recipients.Value, source); - } - - internal virtual void UpdateUsers(API.User[] models, UpdateSource source) - { - if (!IsAttached) - { - var users = new ConcurrentDictionary(1, (int)(models.Length * 1.05)); - for (int i = 0; i < models.Length; i++) - users[models[i].Id] = new GroupUser(this, new User(models[i])); - _users = users; - } - } - - public async Task UpdateAsync() - { - if (IsAttached) throw new NotSupportedException(); - - var model = await Discord.ApiClient.GetChannelAsync(Id).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task LeaveAsync() - { - await Discord.ApiClient.DeleteChannelAsync(Id).ConfigureAwait(false); - } - - public async Task AddUserAsync(IUser user) - { - await Discord.ApiClient.AddGroupRecipientAsync(Id, user.Id).ConfigureAwait(false); - } - public async Task GetUserAsync(ulong id) - { - GroupUser user; - if (_users.TryGetValue(id, out user)) - return user; - var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); - if (id == currentUser.Id) - return currentUser; - return null; - } - public async Task> GetUsersAsync() - { - var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); - return _users.Select(x => x.Value).Concat(ImmutableArray.Create(currentUser)).ToReadOnlyCollection(_users); - } - - public async Task SendMessageAsync(string text, bool isTTS) - { - var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - public async Task SendFileAsync(string filePath, string text, bool isTTS) - { - string filename = Path.GetFileName(filePath); - using (var file = File.OpenRead(filePath)) - { - var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - } - public async Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - { - var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - public virtual async Task GetMessageAsync(ulong id) - { - var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); - if (model != null) - return CreateIncomingMessage(model); - return null; - } - public virtual async Task> GetMessagesAsync(int limit) - { - var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - public virtual async Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - { - var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; - var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - public async Task DeleteMessagesAsync(IEnumerable messages) - { - await Discord.ApiClient.DeleteDMMessagesAsync(Id, new DeleteMessagesParams { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); - } - public async Task> GetPinnedMessagesAsync() - { - var models = await Discord.ApiClient.GetPinsAsync(Id); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - - public async Task TriggerTypingAsync() - { - await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); - } - - private UserMessage CreateOutgoingMessage(MessageModel model) - { - return new UserMessage(this, new User(model.Author.Value), model); - } - private Message CreateIncomingMessage(MessageModel model) - { - if (model.Type == MessageType.Default) - return new UserMessage(this, new User(model.Author.Value), model); - else - return new SystemMessage(this, new User(model.Author.Value), model); - } - - public override string ToString() => Name; - private string DebuggerDisplay => $"@{Name} ({Id}, Group)"; - - IMessage IMessageChannel.GetCachedMessage(ulong id) => null; - } -} diff --git a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs deleted file mode 100644 index 5d97bca7c..000000000 --- a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs +++ /dev/null @@ -1,133 +0,0 @@ -using Discord.API.Rest; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Model = Discord.API.Channel; -using MessageModel = Discord.API.Message; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class TextChannel : GuildChannel, ITextChannel - { - public string Topic { get; private set; } - - public string Mention => MentionUtils.Mention(this); - public virtual IReadOnlyCollection CachedMessages => ImmutableArray.Create(); - - public TextChannel(Guild guild, Model model) - : base(guild, model) - { - } - public override void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - Topic = model.Topic.Value; - base.Update(model, source); - } - - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyTextChannelParams(); - func(args); - - if (!args._name.IsSpecified) - args._name = Name; - - var model = await Discord.ApiClient.ModifyGuildChannelAsync(Id, args).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - - public override async Task GetUserAsync(ulong id) - { - var user = await Guild.GetUserAsync(id).ConfigureAwait(false); - if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages)) - return user; - return null; - } - public override async Task> GetUsersAsync() - { - var users = await Guild.GetUsersAsync().ConfigureAwait(false); - return users.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray(); - } - - public async Task SendMessageAsync(string text, bool isTTS) - { - var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.CreateMessageAsync(Guild.Id, Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - public async Task SendFileAsync(string filePath, string text, bool isTTS) - { - string filename = Path.GetFileName(filePath); - using (var file = File.OpenRead(filePath)) - { - var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - } - public async Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - { - var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, args).ConfigureAwait(false); - return CreateOutgoingMessage(model); - } - public virtual async Task GetMessageAsync(ulong id) - { - var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); - if (model != null) - return CreateIncomingMessage(model); - return null; - } - public virtual async Task> GetMessagesAsync(int limit) - { - var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - public virtual async Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - { - var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; - var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - public async Task DeleteMessagesAsync(IEnumerable messages) - { - await Discord.ApiClient.DeleteMessagesAsync(Guild.Id, Id, new DeleteMessagesParams { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); - } - public async Task> GetPinnedMessagesAsync() - { - var models = await Discord.ApiClient.GetPinsAsync(Id); - return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); - } - - public async Task TriggerTypingAsync() - { - await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); - } - - private UserMessage CreateOutgoingMessage(MessageModel model) - { - return new UserMessage(this, new User(model.Author.Value), model); - } - private Message CreateIncomingMessage(MessageModel model) - { - if (model.Type == MessageType.Default) - return new UserMessage(this, new User(model.Author.Value), model); - else - return new SystemMessage(this, new User(model.Author.Value), model); - } - - private string DebuggerDisplay => $"{Name} ({Id}, Text)"; - - IMessage IMessageChannel.GetCachedMessage(ulong id) => null; - } -} diff --git a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs deleted file mode 100644 index abe4fa56c..000000000 --- a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Discord.API.Rest; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading.Tasks; -using Model = Discord.API.Channel; -using Discord.Audio; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class VoiceChannel : GuildChannel, IVoiceChannel - { - public int Bitrate { get; private set; } - public int UserLimit { get; private set; } - - public VoiceChannel(Guild guild, Model model) - : base(guild, model) - { - } - public override void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - base.Update(model, source); - Bitrate = model.Bitrate.Value; - UserLimit = model.UserLimit.Value; - } - - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyVoiceChannelParams(); - func(args); - - if (!args._name.IsSpecified) - args._name = Name; - - var model = await Discord.ApiClient.ModifyGuildChannelAsync(Id, args).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - - public override Task GetUserAsync(ulong id) - { - throw new NotSupportedException(); - } - public override Task> GetUsersAsync() - { - throw new NotSupportedException(); - } - - public virtual Task ConnectAsync() { throw new NotSupportedException(); } - - private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; - } -} diff --git a/src/Discord.Net/Rest/Entities/Entity.cs b/src/Discord.Net/Rest/Entities/Entity.cs deleted file mode 100644 index 6023626f1..000000000 --- a/src/Discord.Net/Rest/Entities/Entity.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Discord.Rest; - -namespace Discord.Rest -{ - internal abstract class Entity : IEntity - where T : IEquatable - { - public T Id { get; } - - public abstract DiscordRestClient Discord { get; } - - internal virtual bool IsAttached => false; - bool IEntity.IsAttached => IsAttached; - - public Entity(T id) - { - Id = id; - } - } -} diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs deleted file mode 100644 index 395504943..000000000 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ /dev/null @@ -1,307 +0,0 @@ -using Discord.API.Rest; -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.Tasks; -using EmbedModel = Discord.API.GuildEmbed; -using Model = Discord.API.Guild; -using RoleModel = Discord.API.Role; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class Guild : SnowflakeEntity, IGuild - { - protected ConcurrentDictionary _roles; - protected string _iconId, _splashId; - - 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 override DiscordRestClient Discord { get; } - public ulong? AFKChannelId { get; private set; } - public ulong? EmbedChannelId { get; private set; } - public ulong OwnerId { get; private set; } - public string VoiceRegionId { get; private set; } - public ImmutableArray Emojis { get; protected set; } - public ImmutableArray Features { get; protected set; } - - public ulong DefaultChannelId => Id; - public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); - public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, _splashId); - - public Role EveryoneRole => GetRole(Id); - public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); - - public Guild(DiscordRestClient discord, Model model) - : base(model.Id) - { - Discord = discord; - - Update(model, UpdateSource.Creation); - } - public void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - AFKChannelId = model.AFKChannelId; - EmbedChannelId = model.EmbedChannelId; - AFKTimeout = model.AFKTimeout; - IsEmbeddable = model.EmbedEnabled; - _iconId = model.Icon; - Name = model.Name; - OwnerId = model.OwnerId; - VoiceRegionId = model.Region; - _splashId = model.Splash; - VerificationLevel = model.VerificationLevel; - MfaLevel = model.MfaLevel; - DefaultMessageNotifications = model.DefaultMessageNotifications; - - if (model.Emojis != null) - { - var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); - for (int i = 0; i < model.Emojis.Length; i++) - emojis.Add(new Emoji(model.Emojis[i])); - Emojis = emojis.ToImmutableArray(); - } - else - Emojis = ImmutableArray.Create(); - - if (model.Features != null) - Features = model.Features.ToImmutableArray(); - else - Features = ImmutableArray.Create(); - - var roles = new ConcurrentDictionary(1, model.Roles?.Length ?? 0); - if (model.Roles != null) - { - for (int i = 0; i < model.Roles.Length; i++) - roles[model.Roles[i].Id] = new Role(this, model.Roles[i]); - } - _roles = roles; - } - public void Update(EmbedModel model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - IsEmbeddable = model.Enabled; - EmbedChannelId = model.ChannelId; - } - public void Update(IEnumerable models, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - Role role; - foreach (var model in models) - { - if (_roles.TryGetValue(model.Id, out role)) - role.Update(model, UpdateSource.Rest); - } - } - - public async Task UpdateAsync() - { - if (IsAttached) throw new NotSupportedException(); - - var response = await Discord.ApiClient.GetGuildAsync(Id).ConfigureAwait(false); - Update(response, UpdateSource.Rest); - } - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyGuildParams(); - func(args); - - if (args._splash.IsSpecified && _splashId != null) - args._splash = new API.Image(_splashId); - if (args._icon.IsSpecified && _iconId != null) - args._icon = new API.Image(_iconId); - - var model = await Discord.ApiClient.ModifyGuildAsync(Id, args).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task ModifyEmbedAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyGuildEmbedParams(); - func(args); - var model = await Discord.ApiClient.ModifyGuildEmbedAsync(Id, args).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task ModifyChannelsAsync(IEnumerable args) - { - await Discord.ApiClient.ModifyGuildChannelsAsync(Id, args).ConfigureAwait(false); - } - public async Task ModifyRolesAsync(IEnumerable args) - { - var models = await Discord.ApiClient.ModifyGuildRolesAsync(Id, args).ConfigureAwait(false); - Update(models, UpdateSource.Rest); - } - public async Task LeaveAsync() - { - await Discord.ApiClient.LeaveGuildAsync(Id).ConfigureAwait(false); - } - public async Task DeleteAsync() - { - await Discord.ApiClient.DeleteGuildAsync(Id).ConfigureAwait(false); - } - - public async Task> GetBansAsync() - { - var models = await Discord.ApiClient.GetGuildBansAsync(Id).ConfigureAwait(false); - return models.Select(x => new Ban(new User(x.User), x.Reason)).ToImmutableArray(); - } - public Task AddBanAsync(IUser user, int pruneDays = 0) => AddBanAsync(user.Id, pruneDays); - public async Task AddBanAsync(ulong userId, int pruneDays = 0) - { - var args = new CreateGuildBanParams() { DeleteMessageDays = pruneDays }; - await Discord.ApiClient.CreateGuildBanAsync(Id, userId, args).ConfigureAwait(false); - } - public Task RemoveBanAsync(IUser user) => RemoveBanAsync(user.Id); - public async Task RemoveBanAsync(ulong userId) - { - await Discord.ApiClient.RemoveGuildBanAsync(Id, userId).ConfigureAwait(false); - } - - public virtual async Task GetChannelAsync(ulong id) - { - var model = await Discord.ApiClient.GetChannelAsync(Id, id).ConfigureAwait(false); - if (model != null) - return ToChannel(model); - return null; - } - public virtual async Task> GetChannelsAsync() - { - var models = await Discord.ApiClient.GetGuildChannelsAsync(Id).ConfigureAwait(false); - return models.Select(x => ToChannel(x)).ToImmutableArray(); - } - public async Task CreateTextChannelAsync(string name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - - var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; - var model = await Discord.ApiClient.CreateGuildChannelAsync(Id, args).ConfigureAwait(false); - return new TextChannel(this, model); - } - public async Task CreateVoiceChannelAsync(string name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - - var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; - var model = await Discord.ApiClient.CreateGuildChannelAsync(Id, args).ConfigureAwait(false); - return new VoiceChannel(this, model); - } - - public async Task> GetIntegrationsAsync() - { - var models = await Discord.ApiClient.GetGuildIntegrationsAsync(Id).ConfigureAwait(false); - return models.Select(x => new GuildIntegration(this, x)).ToImmutableArray(); - } - public async Task CreateIntegrationAsync(ulong id, string type) - { - var args = new CreateGuildIntegrationParams { Id = id, Type = type }; - var model = await Discord.ApiClient.CreateGuildIntegrationAsync(Id, args).ConfigureAwait(false); - return new GuildIntegration(this, model); - } - - public async Task> GetInvitesAsync() - { - var models = await Discord.ApiClient.GetGuildInvitesAsync(Id).ConfigureAwait(false); - return models.Select(x => new InviteMetadata(Discord, x)).ToImmutableArray(); - } - - public Role GetRole(ulong id) - { - Role result = null; - if (_roles?.TryGetValue(id, out result) == true) - return result; - return null; - } - public async Task CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - - var model = await Discord.ApiClient.CreateGuildRoleAsync(Id).ConfigureAwait(false); - var role = new Role(this, model); - - await role.ModifyAsync(x => - { - x.Name = name; - x.Permissions = (permissions ?? role.Permissions).RawValue; - x.Color = (color ?? Color.Default).RawValue; - x.Hoist = isHoisted; - }).ConfigureAwait(false); - - return role; - } - - public virtual async Task GetUserAsync(ulong id) - { - var model = await Discord.ApiClient.GetGuildMemberAsync(Id, id).ConfigureAwait(false); - if (model != null) - return new GuildUser(this, new User(model.User), model); - return null; - } - public virtual async Task GetCurrentUserAsync() - { - var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); - return await GetUserAsync(currentUser.Id).ConfigureAwait(false); - } - public virtual async Task> GetUsersAsync() - { - var args = new GetGuildMembersParams(); - var models = await Discord.ApiClient.GetGuildMembersAsync(Id, args).ConfigureAwait(false); - return models.Select(x => new GuildUser(this, new User(x.User), x)).ToImmutableArray(); - } - public async Task PruneUsersAsync(int days = 30, bool simulate = false) - { - var args = new GuildPruneParams() { Days = days }; - GetGuildPruneCountResponse model; - if (simulate) - model = await Discord.ApiClient.GetGuildPruneCountAsync(Id, args).ConfigureAwait(false); - else - model = await Discord.ApiClient.BeginGuildPruneAsync(Id, args).ConfigureAwait(false); - return model.Pruned; - } - public virtual Task DownloadUsersAsync() - { - throw new NotSupportedException(); - } - - internal GuildChannel ToChannel(API.Channel model) - { - switch (model.Type) - { - case ChannelType.Text: - return new TextChannel(this, model); - case ChannelType.Voice: - return new VoiceChannel(this, model); - default: - throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); - } - } - - public override string ToString() => Name; - - private string DebuggerDisplay => $"{Name} ({Id})"; - - bool IGuild.Available => false; - IRole IGuild.EveryoneRole => EveryoneRole; - IReadOnlyCollection IGuild.Emojis => Emojis; - IReadOnlyCollection IGuild.Features => Features; - IAudioClient IGuild.AudioClient => null; - - IRole IGuild.GetRole(ulong id) => GetRole(id); - } -} diff --git a/src/Discord.Net/Rest/Entities/Guilds/VoiceRegion.cs b/src/Discord.Net/Rest/Entities/Guilds/VoiceRegion.cs deleted file mode 100644 index 8f8bbfc53..000000000 --- a/src/Discord.Net/Rest/Entities/Guilds/VoiceRegion.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Diagnostics; -using Model = Discord.API.VoiceRegion; - -namespace Discord.Rest -{ - [DebuggerDisplay("{DebuggerDisplay,nq}")] - internal class VoiceRegion : IVoiceRegion - { - public string Id { get; } - public string Name { get; } - public bool IsVip { get; } - public bool IsOptimal { get; } - public string SampleHostname { get; } - public int SamplePort { get; } - - public VoiceRegion(Model model) - { - Id = model.Id; - Name = model.Name; - IsVip = model.IsVip; - IsOptimal = model.IsOptimal; - SampleHostname = model.SampleHostname; - SamplePort = model.SamplePort; - } - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}{(IsVip ? ", VIP" : "")}{(IsOptimal ? ", Optimal" : "")})"; - } -} diff --git a/src/Discord.Net/Rest/Entities/Messages/Attachment.cs b/src/Discord.Net/Rest/Entities/Messages/Attachment.cs deleted file mode 100644 index b5a94689e..000000000 --- a/src/Discord.Net/Rest/Entities/Messages/Attachment.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Model = Discord.API.Attachment; - -namespace Discord.Rest -{ - internal 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; } - - public Attachment(Model model) - { - Id = model.Id; - Filename = model.Filename; - Size = model.Size; - Url = model.Url; - ProxyUrl = model.ProxyUrl; - Height = model.Height.IsSpecified ? model.Height.Value : (int?)null; - Width = model.Width.IsSpecified ? model.Width.Value : (int?)null; - } - } -} diff --git a/src/Discord.Net/Rest/Entities/Messages/Embed.cs b/src/Discord.Net/Rest/Entities/Messages/Embed.cs deleted file mode 100644 index 77c8e28b7..000000000 --- a/src/Discord.Net/Rest/Entities/Messages/Embed.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Model = Discord.API.Embed; - -namespace Discord.Rest -{ - internal class Embed : IEmbed - { - public string Description { get; } - public string Url { get; } - public string Title { get; } - public string Type { get; } - public EmbedProvider Provider { get; } - public EmbedThumbnail Thumbnail { get; } - - public Embed(Model model) - { - Url = model.Url; - Type = model.Type; - Title = model.Title; - Description = model.Description; - - if (model.Provider.IsSpecified) - Provider = new EmbedProvider(model.Provider.Value); - if (model.Thumbnail.IsSpecified) - Thumbnail = new EmbedThumbnail(model.Thumbnail.Value); - } - } -} diff --git a/src/Discord.Net/Rest/Entities/Messages/Message.cs b/src/Discord.Net/Rest/Entities/Messages/Message.cs deleted file mode 100644 index 1b5c025e9..000000000 --- a/src/Discord.Net/Rest/Entities/Messages/Message.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Discord.API.Rest; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading.Tasks; -using Model = Discord.API.Message; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal abstract class Message : SnowflakeEntity, IMessage - { - private long _timestampTicks; - - public IMessageChannel Channel { get; } - public IUser Author { get; } - - public string Content { get; private set; } - - public override DiscordRestClient Discord => (Channel as Entity).Discord; - - 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 MentionedRoles => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); - - public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - - public Message(IMessageChannel channel, IUser author, Model model) - : base(model.Id) - { - Channel = channel; - Author = author; - - Update(model, UpdateSource.Creation); - } - public virtual void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - var guildChannel = Channel as GuildChannel; - var guild = guildChannel?.Guild; - - if (model.Timestamp.IsSpecified) - _timestampTicks = model.Timestamp.Value.UtcTicks; - - if (model.Content.IsSpecified) - Content = model.Content.Value; - } - - public async Task UpdateAsync() - { - if (IsAttached) throw new NotSupportedException(); - - var model = await Discord.ApiClient.GetChannelMessageAsync(Channel.Id, Id).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyMessageParams(); - func(args); - var guildChannel = Channel as GuildChannel; - - Model model; - if (guildChannel != null) - model = await Discord.ApiClient.ModifyMessageAsync(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); - else - model = await Discord.ApiClient.ModifyDMMessageAsync(Channel.Id, Id, args).ConfigureAwait(false); - - Update(model, UpdateSource.Rest); - } - public async Task DeleteAsync() - { - var guildChannel = Channel as GuildChannel; - if (guildChannel != null) - await Discord.ApiClient.DeleteMessageAsync(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); - else - await Discord.ApiClient.DeleteDMMessageAsync(Channel.Id, Id).ConfigureAwait(false); - } - public async Task PinAsync() - { - await Discord.ApiClient.AddPinAsync(Channel.Id, Id).ConfigureAwait(false); - } - public async Task UnpinAsync() - { - await Discord.ApiClient.RemovePinAsync(Channel.Id, Id).ConfigureAwait(false); - } - - public override string ToString() => Content; - private string DebuggerDisplay => $"{Author}: {Content}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; - } -} diff --git a/src/Discord.Net/Rest/Entities/Messages/SystemMessage.cs b/src/Discord.Net/Rest/Entities/Messages/SystemMessage.cs deleted file mode 100644 index a715c6493..000000000 --- a/src/Discord.Net/Rest/Entities/Messages/SystemMessage.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Diagnostics; -using Model = Discord.API.Message; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class SystemMessage : Message, ISystemMessage - { - public MessageType Type { get; } - - public override DiscordRestClient Discord => (Channel as Entity).Discord; - - public SystemMessage(IMessageChannel channel, IUser author, Model model) - : base(channel, author, model) - { - Type = model.Type; - } - - public override string ToString() => Content; - private string DebuggerDisplay => $"[{Type}] {Author}{(!string.IsNullOrEmpty(Content) ? $": ({Content})" : "")}"; - } -} diff --git a/src/Discord.Net/Rest/Entities/Messages/UserMessage.cs b/src/Discord.Net/Rest/Entities/Messages/UserMessage.cs deleted file mode 100644 index 06c904232..000000000 --- a/src/Discord.Net/Rest/Entities/Messages/UserMessage.cs +++ /dev/null @@ -1,175 +0,0 @@ -using Discord.API.Rest; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading.Tasks; -using Model = Discord.API.Message; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class UserMessage : Message, IUserMessage - { - private bool _isMentioningEveryone, _isTTS, _isPinned; - private long? _editedTimestampTicks; - private IReadOnlyCollection _attachments; - private IReadOnlyCollection _embeds; - private IReadOnlyCollection _mentionedChannelIds; - private IReadOnlyCollection _mentionedRoles; - private IReadOnlyCollection _mentionedUsers; - - public override DiscordRestClient Discord => (Channel as Entity).Discord; - 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 => _mentionedChannelIds; - public override IReadOnlyCollection MentionedRoles => _mentionedRoles; - public override IReadOnlyCollection MentionedUsers => _mentionedUsers; - - public UserMessage(IMessageChannel channel, IUser author, Model model) - : base(channel, author, model) - { - _mentionedChannelIds = ImmutableArray.Create(); - _mentionedRoles = ImmutableArray.Create(); - _mentionedUsers = ImmutableArray.Create(); - - Update(model, UpdateSource.Creation); - } - public override void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - var guildChannel = Channel as GuildChannel; - var guild = guildChannel?.Guild; - - if (model.IsTextToSpeech.IsSpecified) - _isTTS = model.IsTextToSpeech.Value; - if (model.Pinned.IsSpecified) - _isPinned = model.Pinned.Value; - if (model.EditedTimestamp.IsSpecified) - _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; - if (model.MentionEveryone.IsSpecified) - _isMentioningEveryone = model.MentionEveryone.Value; - - if (model.Attachments.IsSpecified) - { - var value = model.Attachments.Value; - if (value.Length > 0) - { - var attachments = new Attachment[value.Length]; - for (int i = 0; i < attachments.Length; i++) - attachments[i] = new Attachment(value[i]); - _attachments = ImmutableArray.Create(attachments); - } - else - _attachments = ImmutableArray.Create(); - } - - if (model.Embeds.IsSpecified) - { - var value = model.Embeds.Value; - if (value.Length > 0) - { - var embeds = new Embed[value.Length]; - for (int i = 0; i < embeds.Length; i++) - embeds[i] = new Embed(value[i]); - _embeds = ImmutableArray.Create(embeds); - } - else - _embeds = ImmutableArray.Create(); - } - - ImmutableArray mentions = ImmutableArray.Create(); - if (model.Mentions.IsSpecified) - { - var value = model.Mentions.Value; - if (value.Length > 0) - { - var newMentions = new IUser[value.Length]; - for (int i = 0; i < value.Length; i++) - newMentions[i] = new User(value[i]); - mentions = ImmutableArray.Create(newMentions); - } - } - - if (model.Content.IsSpecified) - { - var text = model.Content.Value; - - if (guildChannel != null) - { - _mentionedUsers = MentionUtils.GetUserMentions(text, Channel, mentions); - _mentionedChannelIds = MentionUtils.GetChannelMentions(text, guildChannel.Guild); - _mentionedRoles = MentionUtils.GetRoleMentions(text, guildChannel.Guild); - } - model.Content = text; - } - - base.Update(model, source); - } - - public async Task UpdateAsync() - { - if (IsAttached) throw new NotSupportedException(); - - var model = await Discord.ApiClient.GetChannelMessageAsync(Channel.Id, Id).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyMessageParams(); - func(args); - var guildChannel = Channel as GuildChannel; - - Model model; - if (guildChannel != null) - model = await Discord.ApiClient.ModifyMessageAsync(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); - else - model = await Discord.ApiClient.ModifyDMMessageAsync(Channel.Id, Id, args).ConfigureAwait(false); - - Update(model, UpdateSource.Rest); - } - public async Task DeleteAsync() - { - var guildChannel = Channel as GuildChannel; - if (guildChannel != null) - await Discord.ApiClient.DeleteMessageAsync(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); - else - await Discord.ApiClient.DeleteDMMessageAsync(Channel.Id, Id).ConfigureAwait(false); - } - public async Task PinAsync() - { - await Discord.ApiClient.AddPinAsync(Channel.Id, Id).ConfigureAwait(false); - } - public async Task UnpinAsync() - { - await Discord.ApiClient.RemovePinAsync(Channel.Id, Id).ConfigureAwait(false); - } - - public string Resolve(int startIndex, int length, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, - RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) - => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(UserMentionHandling userHandling, ChannelMentionHandling channelHandling, - RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) - => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); - - private string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, - RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) - { - text = MentionUtils.ResolveUserMentions(text, Channel, MentionedUsers, userHandling); - text = MentionUtils.ResolveChannelMentions(text, (Channel as IGuildChannel)?.Guild, channelHandling); - text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling); - text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling); - return text; - } - - public override string ToString() => Content; - private string DebuggerDisplay => $"{Author}: {Content}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; - } -} diff --git a/src/Discord.Net/Rest/Entities/Roles/Role.cs b/src/Discord.Net/Rest/Entities/Roles/Role.cs deleted file mode 100644 index dcb198cd4..000000000 --- a/src/Discord.Net/Rest/Entities/Roles/Role.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Discord.API.Rest; -using Discord.Rest; -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using Model = Discord.API.Role; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class Role : SnowflakeEntity, IRole, IMentionable - { - public Guild Guild { get; } - - public Color Color { get; private set; } - public bool IsHoisted { get; private set; } - public bool IsManaged { get; private set; } - public string Name { get; private set; } - public GuildPermissions Permissions { get; private set; } - public int Position { get; private set; } - - public bool IsEveryone => Id == Guild.Id; - public string Mention => MentionUtils.Mention(this); - public override DiscordRestClient Discord => Guild.Discord; - - public Role(Guild guild, Model model) - : base(model.Id) - { - Guild = guild; - - Update(model, UpdateSource.Creation); - } - public void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - Name = model.Name; - IsHoisted = model.Hoist; - IsManaged = model.Managed; - Position = model.Position; - Color = new Color(model.Color); - Permissions = new GuildPermissions(model.Permissions); - } - - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyGuildRoleParams(); - func(args); - var response = await Discord.ApiClient.ModifyGuildRoleAsync(Guild.Id, Id, args).ConfigureAwait(false); - - Update(response, UpdateSource.Rest); - } - public async Task DeleteAsync() - { - await Discord.ApiClient.DeleteGuildRoleAsync(Guild.Id, Id).ConfigureAwait(false); - } - - public Role Clone() => MemberwiseClone() as Role; - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; - - ulong IRole.GuildId => Guild.Id; - } -} diff --git a/src/Discord.Net/Rest/Entities/SnowflakeEntity.cs b/src/Discord.Net/Rest/Entities/SnowflakeEntity.cs deleted file mode 100644 index f126c8ff5..000000000 --- a/src/Discord.Net/Rest/Entities/SnowflakeEntity.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Discord.Rest -{ - internal abstract class SnowflakeEntity : Entity, ISnowflakeEntity - { - //TODO: C#7 Candidate for Extension Property. Lets us remove this class. - public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); - - public SnowflakeEntity(ulong id) - : base(id) - { - } - } -} diff --git a/src/Discord.Net/Rest/Entities/Users/Connection.cs b/src/Discord.Net/Rest/Entities/Users/Connection.cs deleted file mode 100644 index 622bc8730..000000000 --- a/src/Discord.Net/Rest/Entities/Users/Connection.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using Model = Discord.API.Connection; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class Connection : IConnection - { - public string Id { get; } - public string Type { get; } - public string Name { get; } - public bool IsRevoked { get; } - - public IReadOnlyCollection IntegrationIds { get; } - - public Connection(Model model) - { - Id = model.Id; - Type = model.Type; - Name = model.Name; - IsRevoked = model.Revoked; - - IntegrationIds = model.Integrations; - } - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}, Type = {Type}{(IsRevoked ? ", Revoked" : "")})"; - } -} diff --git a/src/Discord.Net/Rest/Entities/Users/GroupUser.cs b/src/Discord.Net/Rest/Entities/Users/GroupUser.cs deleted file mode 100644 index 5a0df5cc2..000000000 --- a/src/Discord.Net/Rest/Entities/Users/GroupUser.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Discord.API.Rest; -using Discord.Rest; -using System; -using System.Threading.Tasks; - -namespace Discord.Rest -{ - internal class GroupUser : IGroupUser - { - internal virtual bool IsAttached => false; - bool IEntity.IsAttached => IsAttached; - - public GroupChannel Channel { get; private set; } - public User User { get; private set; } - - public ulong Id => User.Id; - public string AvatarUrl => User.AvatarUrl; - public DateTimeOffset CreatedAt => User.CreatedAt; - public string Discriminator => User.Discriminator; - public ushort DiscriminatorValue => User.DiscriminatorValue; - public bool IsBot => User.IsBot; - public string Username => User.Username; - public string Mention => MentionUtils.Mention(this, false); - - public virtual UserStatus Status => UserStatus.Unknown; - public virtual Game Game => null; - - public DiscordRestClient Discord => Channel.Discord; - - public GroupUser(GroupChannel channel, User user) - { - Channel = channel; - User = user; - } - - public async Task KickAsync() - { - await Discord.ApiClient.RemoveGroupRecipientAsync(Channel.Id, Id).ConfigureAwait(false); - } - - public async Task CreateDMChannelAsync() - { - var args = new CreateDMChannelParams { Recipient = this }; - var model = await Discord.ApiClient.CreateDMChannelAsync(args).ConfigureAwait(false); - - return new DMChannel(Discord, new User(model.Recipients.Value[0]), model); - } - } -} diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs deleted file mode 100644 index 1c8b2cfc1..000000000 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Discord.API.Rest; -using Discord.Rest; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Model = Discord.API.GuildMember; -using PresenceModel = Discord.API.Presence; - -namespace Discord.Rest -{ - [DebuggerDisplay("{DebuggerDisplay,nq}")] - internal class GuildUser : IGuildUser, ISnowflakeEntity - { - internal virtual bool IsAttached => false; - bool IEntity.IsAttached => IsAttached; - - private long? _joinedAtTicks; - - public string Nickname { get; private set; } - public GuildPermissions GuildPermissions { get; private set; } - - public Guild Guild { get; private set; } - public User User { get; private set; } - public ImmutableArray Roles { get; private set; } - - public ulong Id => User.Id; - public string AvatarUrl => User.AvatarUrl; - public DateTimeOffset CreatedAt => User.CreatedAt; - public string Discriminator => User.Discriminator; - public ushort DiscriminatorValue => User.DiscriminatorValue; - public bool IsBot => User.IsBot; - public string Mention => MentionUtils.Mention(this, Nickname != null); - public string Username => User.Username; - - public virtual UserStatus Status => UserStatus.Unknown; - public virtual Game Game => null; - - public DiscordRestClient Discord => Guild.Discord; - public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); - - public GuildUser(Guild guild, User user) - { - Guild = guild; - User = user; - Roles = ImmutableArray.Create(); - } - public GuildUser(Guild guild, User user, Model model) - : this(guild, user) - { - Update(model, UpdateSource.Creation); - } - public GuildUser(Guild guild, User user, PresenceModel model) - : this(guild, user) - { - Update(model, UpdateSource.Creation); - } - public void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - //if (model.JoinedAt.IsSpecified) - _joinedAtTicks = model.JoinedAt.UtcTicks; - if (model.Nick.IsSpecified) - Nickname = model.Nick.Value; - - //if (model.Roles.IsSpecified) - UpdateRoles(model.Roles); - } - public virtual void Update(PresenceModel model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - if (model.Roles.IsSpecified) - UpdateRoles(model.Roles.Value); - if (model.Nick.IsSpecified) - Nickname = model.Nick.Value; - } - private void Update(ModifyGuildMemberParams args, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - if (args._roleIds.IsSpecified) - Roles = args._roleIds.Value.Select(x => Guild.GetRole(x)).Where(x => x != null).ToImmutableArray(); - if (args._nickname.IsSpecified) - Nickname = args._nickname.Value ?? ""; - } - private void UpdateRoles(ulong[] roleIds) - { - var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1); - roles.Add(Guild.EveryoneRole); - for (int i = 0; i < roleIds.Length; i++) - { - var role = Guild.GetRole(roleIds[i]); - if (role != null) - roles.Add(role); - } - Roles = roles.ToImmutable(); - GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this)); - } - - public async Task UpdateAsync() - { - if (IsAttached) throw new NotSupportedException(); - - var model = await Discord.ApiClient.GetGuildMemberAsync(Guild.Id, Id).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyGuildMemberParams(); - func(args); - - bool isCurrentUser = (await Discord.GetCurrentUserAsync().ConfigureAwait(false)).Id == Id; - if (isCurrentUser && args._nickname.IsSpecified) - { - var nickArgs = new ModifyCurrentUserNickParams { Nickname = args._nickname.Value ?? "" }; - await Discord.ApiClient.ModifyMyNickAsync(Guild.Id, nickArgs).ConfigureAwait(false); - args._nickname = Optional.Create(); //Remove - } - - if (!isCurrentUser || args._deaf.IsSpecified || args._mute.IsSpecified || args._roleIds.IsSpecified) - { - await Discord.ApiClient.ModifyGuildMemberAsync(Guild.Id, Id, args).ConfigureAwait(false); - Update(args, UpdateSource.Rest); - } - } - public async Task KickAsync() - { - await Discord.ApiClient.RemoveGuildMemberAsync(Guild.Id, Id).ConfigureAwait(false); - } - - public override string ToString() => $"{Username}#{Discriminator}"; - private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; - - public ChannelPermissions GetPermissions(IGuildChannel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue)); - } - - public async Task CreateDMChannelAsync() - { - var args = new CreateDMChannelParams { Recipient = this }; - var model = await Discord.ApiClient.CreateDMChannelAsync(args).ConfigureAwait(false); - - return new DMChannel(Discord, new User(model.Recipients.Value[0]), model); - } - - IGuild IGuildUser.Guild => Guild; - IReadOnlyCollection IGuildUser.Roles => Roles; - bool IVoiceState.IsDeafened => false; - bool IVoiceState.IsMuted => false; - bool IVoiceState.IsSelfDeafened => false; - bool IVoiceState.IsSelfMuted => false; - bool IVoiceState.IsSuppressed => false; - IVoiceChannel IVoiceState.VoiceChannel => null; - string IVoiceState.VoiceSessionId => null; - } -} diff --git a/src/Discord.Net/Rest/Entities/Users/SelfUser.cs b/src/Discord.Net/Rest/Entities/Users/SelfUser.cs deleted file mode 100644 index ee32fa243..000000000 --- a/src/Discord.Net/Rest/Entities/Users/SelfUser.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Discord.API.Rest; -using Discord.Rest; -using System; -using System.Threading.Tasks; -using Model = Discord.API.User; - -namespace Discord.Rest -{ - internal class SelfUser : User, ISelfUser - { - protected long _idleSince; - protected UserStatus _status; - protected Game _game; - - public string Email { get; private set; } - public bool IsVerified { get; private set; } - public bool IsMfaEnabled { get; private set; } - - public override UserStatus Status => _status; - public override Game Game => _game; - - public override DiscordRestClient Discord { get; } - - public SelfUser(DiscordRestClient discord, Model model) - : base(model) - { - Discord = discord; - } - public override void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - base.Update(model, source); - - if (model.Email.IsSpecified) - Email = model.Email.Value; - if (model.Verified.IsSpecified) - IsVerified = model.Verified.Value; - if (model.MfaEnabled.IsSpecified) - IsMfaEnabled = model.MfaEnabled.Value; - } - - public async Task UpdateAsync() - { - if (IsAttached) throw new NotSupportedException(); - - var model = await Discord.ApiClient.GetMyUserAsync().ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyCurrentUserParams(); - func(args); - - if (!args._username.IsSpecified) - args._username = Username; - if (!args._avatar.IsSpecified && _avatarId != null) - args._avatar = new API.Image(_avatarId); - - var model = await Discord.ApiClient.ModifySelfAsync(args).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - - Task ISelfUser.ModifyStatusAsync(Action func) { throw new NotSupportedException(); } - } -} diff --git a/src/Discord.Net/Rest/Entities/Users/User.cs b/src/Discord.Net/Rest/Entities/Users/User.cs deleted file mode 100644 index 8f0877b59..000000000 --- a/src/Discord.Net/Rest/Entities/Users/User.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Discord.Rest; -using System; -using System.Diagnostics; -using Model = Discord.API.User; - -namespace Discord.Rest -{ - [DebuggerDisplay("{DebuggerDisplay,nq}")] - internal class User : SnowflakeEntity, IUser - { - protected string _avatarId; - - public bool IsBot { get; private set; } - public string Username { get; private set; } - public ushort DiscriminatorValue { get; private set; } - - public override DiscordRestClient Discord { get { throw new NotSupportedException(); } } - - public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); - public string Discriminator => DiscriminatorValue.ToString("D4"); - public string Mention => MentionUtils.Mention(this); - public virtual Game Game => null; - public virtual UserStatus Status => UserStatus.Unknown; - - public User(Model model) - : base(model.Id) - { - Update(model, UpdateSource.Creation); - } - public virtual void Update(Model model, UpdateSource source) - { - if (source == UpdateSource.Rest && IsAttached) return; - - if (model.Avatar.IsSpecified) - _avatarId = model.Avatar.Value; - if (model.Discriminator.IsSpecified) - DiscriminatorValue = ushort.Parse(model.Discriminator.Value); - if (model.Bot.IsSpecified) - IsBot = model.Bot.Value; - if (model.Username.IsSpecified) - Username = model.Username.Value; - } - - public override string ToString() => $"{Username}#{Discriminator}"; - private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; - } -} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/ISocketChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/ISocketChannel.cs deleted file mode 100644 index d74f35faa..000000000 --- a/src/Discord.Net/WebSocket/Entities/Channels/ISocketChannel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Model = Discord.API.Channel; - -namespace Discord.WebSocket -{ - internal interface ISocketChannel : IChannel - { - void Update(Model model, UpdateSource source); - - ISocketChannel Clone(); - } -} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/ISocketGuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/ISocketGuildChannel.cs deleted file mode 100644 index f70a56a53..000000000 --- a/src/Discord.Net/WebSocket/Entities/Channels/ISocketGuildChannel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Discord.WebSocket -{ - internal interface ISocketGuildChannel : ISocketChannel, IGuildChannel - { - new SocketGuild Guild { get; } - } -} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/ISocketMessageChannel.cs deleted file mode 100644 index 80706970f..000000000 --- a/src/Discord.Net/WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using MessageModel = Discord.API.Message; - -namespace Discord.WebSocket -{ - internal interface ISocketMessageChannel : ISocketChannel, IMessageChannel - { - IReadOnlyCollection Users { get; } - - ISocketMessage CreateMessage(ISocketUser author, MessageModel model); - ISocketMessage AddMessage(ISocketUser author, MessageModel model); - ISocketMessage GetMessage(ulong id); - ISocketMessage RemoveMessage(ulong id); - - ISocketUser GetUser(ulong id, bool skipCheck = false); - } -} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/ISocketPrivateChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/ISocketPrivateChannel.cs deleted file mode 100644 index e38107da6..000000000 --- a/src/Discord.Net/WebSocket/Entities/Channels/ISocketPrivateChannel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Discord.WebSocket -{ - internal interface ISocketPrivateChannel : ISocketChannel, IPrivateChannel - { - new IReadOnlyCollection Recipients { get; } - } -} diff --git a/src/Discord.Net/WebSocket/Extensions/ChannelExtensions.cs b/src/Discord.Net/WebSocket/Extensions/ChannelExtensions.cs deleted file mode 100644 index ab9911cac..000000000 --- a/src/Discord.Net/WebSocket/Extensions/ChannelExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Discord.WebSocket -{ - public static class ChannelExtensions - { - public static IUser GetUser(this IDMChannel channel, ulong id) - => GetSocketDMChannel(channel).GetUser(id); - - public static IReadOnlyCollection GetUsers(this IDMChannel channel) - => GetSocketDMChannel(channel).Users; - - public static IUser GetUser(this IGroupChannel channel, ulong id) - => GetSocketGroupChannel(channel).GetUser(id); - - public static IReadOnlyCollection GetUsers(this IGroupChannel channel) - => GetSocketGroupChannel(channel).Users; - - public static IGuildUser GetUser(this ITextChannel channel, ulong id) - => GetSocketTextChannel(channel).GetUser(id); - - public static IReadOnlyCollection GetUsers(this ITextChannel channel) - => GetSocketTextChannel(channel).Members; - - public static IGuildUser GetUser(this IVoiceChannel channel, ulong id) - => GetSocketVoiceChannel(channel).GetUser(id); - - public static IReadOnlyCollection GetUsers(this IVoiceChannel channel) - => GetSocketVoiceChannel(channel).Members; - - internal static SocketDMChannel GetSocketDMChannel(IDMChannel channel) - { - Preconditions.NotNull(channel, nameof(channel)); - var socketChannel = channel as SocketDMChannel; - if (socketChannel == null) - throw new InvalidOperationException("This extension method is only valid on WebSocket Entities"); - return socketChannel; - } - internal static SocketGroupChannel GetSocketGroupChannel(IGroupChannel channel) - { - Preconditions.NotNull(channel, nameof(channel)); - var socketChannel = channel as SocketGroupChannel; - if (socketChannel == null) - throw new InvalidOperationException("This extension method is only valid on WebSocket Entities"); - return socketChannel; - } - internal static SocketTextChannel GetSocketTextChannel(ITextChannel channel) - { - Preconditions.NotNull(channel, nameof(channel)); - var socketChannel = channel as SocketTextChannel; - if (socketChannel == null) - throw new InvalidOperationException("This extension method is only valid on WebSocket Entities"); - return socketChannel; - } - internal static SocketVoiceChannel GetSocketVoiceChannel(IVoiceChannel channel) - { - Preconditions.NotNull(channel, nameof(channel)); - var socketChannel = channel as SocketVoiceChannel; - if (socketChannel == null) - throw new InvalidOperationException("This extension method is only valid on WebSocket Entities"); - return socketChannel; - } - } -} diff --git a/src/Discord.Net/WebSocket/Extensions/DiscordClientExtensions.cs b/src/Discord.Net/WebSocket/Extensions/DiscordClientExtensions.cs deleted file mode 100644 index 8a5cfc9bd..000000000 --- a/src/Discord.Net/WebSocket/Extensions/DiscordClientExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Discord.WebSocket -{ - public static class DiscordClientExtensions - { - public static IPrivateChannel GetPrivateChannel(this DiscordSocketClient client, ulong id) - => client.GetChannel(id) as IPrivateChannel; - - public static IDMChannel GetDMChannel(this DiscordSocketClient client, ulong id) - => client.GetPrivateChannelAsync(id) as IDMChannel; - public static IEnumerable GetDMChannels(this DiscordSocketClient client) - => client.GetPrivateChannels().Select(x => x as IDMChannel).Where(x => x != null); - - public static IGroupChannel GetGroupChannel(this DiscordSocketClient client, ulong id) - => client.GetPrivateChannel(id) as IGroupChannel; - public static IEnumerable GetGroupChannels(this DiscordSocketClient client) - => client.GetPrivateChannels().Select(x => x as IGroupChannel).Where(x => x != null); - - public static IVoiceRegion GetVoiceRegion(this DiscordSocketClient client, string id) - => client.VoiceRegions.FirstOrDefault(r => r.Id == id); - public static IReadOnlyCollection GetVoiceRegions(this DiscordSocketClient client) => - client.VoiceRegions; - public static IVoiceRegion GetOptimalVoiceRegion(this DiscordSocketClient client) - => client.VoiceRegions.FirstOrDefault(x => x.IsOptimal); - - public static IGuild GetGuild(this DiscordSocketClient client, ulong id) => - client.DataStore.GetGuild(id); - public static GuildEmbed? GetGuildEmbed(this DiscordSocketClient client, ulong id) - { - var guild = client.DataStore.GetGuild(id); - if (guild != null) - return new GuildEmbed(guild.IsEmbeddable, guild.EmbedChannelId); - return null; - } - public static IReadOnlyCollection GetGuilds(this DiscordSocketClient client) => - client.Guilds; - - public static IChannel GetChannel(this DiscordSocketClient client, ulong id) => - client.DataStore.GetChannel(id); - public static IReadOnlyCollection GetPrivateChannels(this DiscordSocketClient client) => - client.DataStore.PrivateChannels; - - public static IUser GetUser(this DiscordSocketClient client, ulong id) => - client.DataStore.GetUser(id); - public static IUser GetUser(this DiscordSocketClient client, string username, string discriminator) => - client.DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); - public static ISelfUser GetCurrentUser(this DiscordSocketClient client) => - client.CurrentUser; - - } -} diff --git a/src/Discord.Net/WebSocket/Extensions/GuildExtensions.cs b/src/Discord.Net/WebSocket/Extensions/GuildExtensions.cs deleted file mode 100644 index a2c5c7882..000000000 --- a/src/Discord.Net/WebSocket/Extensions/GuildExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Discord.WebSocket -{ - // Todo: Docstrings - public static class GuildExtensions - { - // Channels - public static IGuildChannel GetChannel(this IGuild guild, ulong id) => - GetSocketGuild(guild).GetChannel(id); - public static IReadOnlyCollection GetChannels(this IGuild guild) => - GetSocketGuild(guild).Channels; - - public static ITextChannel GetTextChannel(this IGuild guild, ulong id) => - GetSocketGuild(guild).GetChannel(id) as ITextChannel; - public static IEnumerable GetTextChannels(this IGuild guild) => - GetSocketGuild(guild).Channels.Select(c => c as ITextChannel).Where(c => c != null); - - - public static IVoiceChannel GetVoiceChannel(this IGuild guild, ulong id) => - GetSocketGuild(guild).GetChannel(id) as IVoiceChannel; - public static IEnumerable GetVoiceChannels(this IGuild guild) => - GetSocketGuild(guild).Channels.Select(c => c as IVoiceChannel).Where(c => c != null); - - // Users - public static IGuildUser GetCurrentUser(this IGuild guild) => - GetSocketGuild(guild).CurrentUser; - public static IGuildUser GetUser(this IGuild guild, ulong id) => - GetSocketGuild(guild).GetUser(id); - - public static IReadOnlyCollection GetUsers(this IGuild guild) => - GetSocketGuild(guild).Members; - - public static int GetUserCount(this IGuild guild) => - GetSocketGuild(guild).MemberCount; - public static int GetCachedUserCount(this IGuild guild) => - GetSocketGuild(guild).DownloadedMemberCount; - - //Helpers - internal static SocketGuild GetSocketGuild(IGuild guild) - { - Preconditions.NotNull(guild, nameof(guild)); - var socketGuild = guild as SocketGuild; - if (socketGuild == null) - throw new InvalidOperationException("This extension method is only valid on WebSocket Entities"); - return socketGuild; - } - } -} diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 52fbc34c9..8185a792d 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,6 +1,6 @@ -{ - "version": "1.0.0-beta-*", - "description": "An unofficial .Net API wrapper for the Discord service.", +{ + "version": "1.0.0-beta2-*", + "description": "An aynchronous API wrapper for Discord using .NET. This package includes all of the optional Discord.Net components", "authors": [ "RogueException" ], "packOptions": { @@ -13,37 +13,19 @@ } }, - "buildOptions": { - "allowUnsafe": true, - "warningsAsErrors": false, - "xmlDoc": true - }, - - "configurations": { - "Release": { - "buildOptions": { - "define": [ "RELEASE" ], - "nowarn": [ "CS1573", "CS1591" ] - } - } - }, - "dependencies": { - "Microsoft.Win32.Primitives": "4.0.1", - "Newtonsoft.Json": "8.0.3", - "System.Collections.Concurrent": "4.0.12", - "System.Collections.Immutable": "1.2.0", - "System.IO.Compression": "4.1.0", - "System.IO.FileSystem": "4.0.1", - "System.Net.Http": "4.1.0", - "System.Net.NameResolution": "4.0.0", - "System.Net.Sockets": "4.1.0", - "System.Net.WebSockets.Client": "4.0.0", - "System.Reflection.Extensions": "4.0.1", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", - "System.Runtime.Serialization.Primitives": "4.1.1", - "System.Text.RegularExpressions": "4.1.0" + "Discord.Net.Rest": { + "target": "project" + } + //"Discord.Net.WebSocket": { + // "target": "project" + //}, + //"Discord.Net.Rpc": { + // "target": "project" + //}, + //"Discord.Net.Commands": { + // "target": "project" + //} }, "frameworks": { From 0c4641ac687c83479332bfa201cded599445f40c Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 22 Sep 2016 21:32:42 -0300 Subject: [PATCH 002/102] Updated versions to beta2 --- src/Discord.Net.Rest/project.json | 5 ++--- src/Discord.Net.Rpc/project.json | 7 +++---- src/Discord.Net/project.json | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.Rest/project.json b/src/Discord.Net.Rest/project.json index 330cf0beb..58cb60473 100644 --- a/src/Discord.Net.Rest/project.json +++ b/src/Discord.Net.Rest/project.json @@ -1,11 +1,10 @@ { - "version": "1.0.0-*", + "version": "1.0.0-beta2-*", "buildOptions": { "compile": { "include": [ "../Discord.Net.Utils/**.cs" ] - }, - "define": [ "REST" ] + } }, "configurations": { diff --git a/src/Discord.Net.Rpc/project.json b/src/Discord.Net.Rpc/project.json index de1e3725c..9a7de9a07 100644 --- a/src/Discord.Net.Rpc/project.json +++ b/src/Discord.Net.Rpc/project.json @@ -1,11 +1,10 @@ { - "version": "1.0.0-*", + "version": "1.0.0-beta2-*", "buildOptions": { "compile": { - "include": [ "../Discord.Net.Entities/**.cs", "../Discord.Net.Utils/**.cs" ] - }, - "define": [ "RPC" ] + "include": [ "../Discord.Net.Utils/**.cs" ] + } }, "dependencies": { diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 16fdb8aef..370fde847 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-beta-*", + "version": "1.0.0-beta2-*", "description": "An unofficial .Net API wrapper for the Discord service.", "authors": [ "RogueException" ], From dd86f03306dcba1131c138a9f930aa070927f337 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 28 Sep 2016 04:12:17 -0300 Subject: [PATCH 003/102] Started converting websocket and rpc classes --- Discord.Net.sln | 5 - src/Discord.Net.Core/AssemblyInfo.cs | 7 + src/Discord.Net.Core/Entities/Guilds/Emoji.cs | 4 +- .../Entities/Messages/EmbedProvider.cs | 4 +- .../Entities/Messages/EmbedThumbnail.cs | 4 +- src/Discord.Net.Core/Entities/Users/Game.cs | 4 +- .../Entities/Users/UserStatus.cs | 2 + src/Discord.Net.Core/Format.cs | 2 - src/Discord.Net.Core/IDiscordClient.cs | 2 - .../Logging/LogManager.cs | 19 + .../Logging/Logger.cs | 0 .../Net/Converters/UserStatusConverter.cs | 10 + .../Utils}/AsyncEvent.cs | 0 .../Utils}/ConcurrentHashSet.cs | 0 .../Utils}/DateTimeUtils.cs | 0 .../Utils}/Extensions/CollectionExtensions.cs | 0 .../Utils}/Extensions/Permissions.cs | 0 .../TaskCompletionSourceExtensions.cs | 0 .../Utils}/MentionsHelper.cs | 0 .../Utils}/Paging/Page.cs | 0 .../Utils}/Paging/PageInfo.cs | 0 .../Utils}/Paging/PagedEnumerator.cs | 0 .../Utils}/Permissions.cs | 0 .../Utils}/Preconditions.cs | 0 src/Discord.Net.Rest/AssemblyInfo.cs | 6 + src/Discord.Net.Rest/ClientHelper.cs | 118 ++++ src/Discord.Net.Rest/DiscordClient.cs | 162 +++++ src/Discord.Net.Rest/DiscordRestClient.cs | 313 ++-------- .../Entities/Channels/ChannelHelper.cs | 44 +- .../Entities/Channels/RestChannel.cs | 48 ++ .../Entities/Channels/RestDMChannel.cs | 21 +- .../Entities/Channels/RestGroupChannel.cs | 10 +- .../Entities/Channels/RestGuildChannel.cs | 10 +- .../Entities/Channels/RestTextChannel.cs | 4 +- .../Entities/Channels/RestVoiceChannel.cs | 4 +- .../Entities/Guilds/GuildHelper.cs | 42 +- .../Entities/Guilds/RestBan.cs | 2 +- .../Entities/Guilds/RestGuild.cs | 4 +- .../Entities/Guilds/RestGuildIntegration.cs | 4 +- .../Entities/Guilds/RestUserGuild.cs | 4 +- .../Entities/Guilds/RestVoiceRegion.cs | 4 +- .../Entities/Invites/InviteHelper.cs | 6 +- .../Entities/Invites/RestInvite.cs | 4 +- .../Entities/Invites/RestInviteMetadata.cs | 4 +- .../Entities/Messages/MessageHelper.cs | 10 +- .../Entities/Messages/RestAttachment.cs | 1 + .../Entities/Messages/RestMessage.cs | 4 +- .../Entities/Messages/RestSystemMessage.cs | 4 +- .../Entities/Messages/RestUserMessage.cs | 4 +- .../Entities/RestApplication.cs | 4 +- src/Discord.Net.Rest/Entities/RestEntity.cs | 4 +- .../Entities/Roles/RestRole.cs | 4 +- .../Entities/Roles/RoleHelper.cs | 4 +- .../Entities/Users/RestGroupUser.cs | 4 +- .../Entities/Users/RestGuildUser.cs | 4 +- .../Entities/Users/RestSelfUser.cs | 4 +- .../Entities/Users/RestUser.cs | 4 +- .../Entities/Users/UserHelper.cs | 14 +- src/Discord.Net.Rest/project.json | 6 - .../API/DiscordRpcApiClient.cs | 5 +- src/Discord.Net.Rpc/AssemblyInfo.cs | 3 + src/Discord.Net.Rpc/DiscordRpcClient.cs | 25 +- .../Entities/IRemoteUserGuild.cs | 8 - .../Entities/Messages/RpcMessage.cs | 10 + src/Discord.Net.Rpc/Entities/RpcEntity.cs | 19 + .../{RemoteUserGuild.cs => RpcGuild.cs} | 4 +- src/Discord.Net.Rpc/Entities/RpcMessage.cs | 15 - src/Discord.Net.Rpc/project.json | 26 +- .../Discord.Net.Utils.projitems | 26 - .../Discord.Net.Utils.shproj | 13 - .../API/DiscordSocketApiClient.cs | 5 +- src/Discord.Net.WebSocket/AssemblyInfo.cs | 3 + .../Audio/AudioClient.cs | 27 +- .../Audio/Streams/OpusDecodeStream.cs | 2 +- .../Audio/Streams/OpusEncodeStream.cs | 2 +- .../Audio/Streams/RTPReadStream.cs | 2 +- .../Audio/Streams/RTPWriteStream.cs | 2 +- src/Discord.Net.WebSocket/DataStore.cs | 22 +- .../Discord.Net.WebSocket.xproj | 6 +- .../DiscordSocketClient.cs | 268 ++++---- .../Entities/Channels/SocketChannel.cs | 45 ++ .../Entities/Channels/SocketDMChannel.cs | 143 +++-- .../Entities/Channels/SocketGroupChannel.cs | 203 +++---- .../Entities/Channels/SocketGuildChannel.cs | 166 ++--- .../Entities/Channels/SocketTextChannel.cs | 136 +++-- .../Entities/Channels/SocketVoiceChannel.cs | 60 +- .../Entities/Guilds/SocketGuild.cs | 573 ++++++------------ .../Entities/Guilds/SocketGuildIntegration.cs | 76 --- .../Entities/Messages/ISocketMessage.cs | 13 - .../{Utilities => Messages}/MessageCache.cs | 41 +- .../Entities/Messages/SocketMessage.cs | 64 ++ .../Entities/Messages/SocketSystemMessage.cs | 23 +- .../Entities/Messages/SocketUserMessage.cs | 129 +++- .../Entities/SocketEntity.cs | 19 + .../Entities/Users/ISocketUser.cs | 9 - .../Entities/Users/Presence.cs | 17 - .../Entities/Users/SocketDMUser.cs | 46 -- .../Entities/Users/SocketGlobalUser.cs | 59 +- .../Entities/Users/SocketGroupUser.cs | 42 +- .../Entities/Users/SocketGuildUser.cs | 91 +-- .../Entities/Users/SocketPresence.cs | 23 + .../Entities/Users/SocketSelfUser.cs | 57 +- .../Entities/Users/SocketUser.cs | 50 ++ .../{VoiceState.cs => SocketVoiceState.cs} | 12 +- .../Entities/Utilities/MessageManager.cs | 92 --- src/Discord.Net.WebSocket/project.json | 28 +- src/Discord.Net/project.json | 50 +- 107 files changed, 1833 insertions(+), 1879 deletions(-) create mode 100644 src/Discord.Net.Core/AssemblyInfo.cs rename src/{Discord.Net.Utils => Discord.Net.Core}/Logging/LogManager.cs (78%) rename src/{Discord.Net.Utils => Discord.Net.Core}/Logging/Logger.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/AsyncEvent.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/ConcurrentHashSet.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/DateTimeUtils.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/Extensions/CollectionExtensions.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/Extensions/Permissions.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/Extensions/TaskCompletionSourceExtensions.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/MentionsHelper.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/Paging/Page.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/Paging/PageInfo.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/Paging/PagedEnumerator.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/Permissions.cs (100%) rename src/{Discord.Net.Utils => Discord.Net.Core/Utils}/Preconditions.cs (100%) create mode 100644 src/Discord.Net.Rest/AssemblyInfo.cs create mode 100644 src/Discord.Net.Rest/ClientHelper.cs create mode 100644 src/Discord.Net.Rest/DiscordClient.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestChannel.cs create mode 100644 src/Discord.Net.Rpc/AssemblyInfo.cs delete mode 100644 src/Discord.Net.Rpc/Entities/IRemoteUserGuild.cs create mode 100644 src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs create mode 100644 src/Discord.Net.Rpc/Entities/RpcEntity.cs rename src/Discord.Net.Rpc/Entities/{RemoteUserGuild.cs => RpcGuild.cs} (78%) delete mode 100644 src/Discord.Net.Rpc/Entities/RpcMessage.cs delete mode 100644 src/Discord.Net.Utils/Discord.Net.Utils.projitems delete mode 100644 src/Discord.Net.Utils/Discord.Net.Utils.shproj create mode 100644 src/Discord.Net.WebSocket/AssemblyInfo.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs delete mode 100644 src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs delete mode 100644 src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs rename src/Discord.Net.WebSocket/Entities/{Utilities => Messages}/MessageCache.cs (59%) create mode 100644 src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs create mode 100644 src/Discord.Net.WebSocket/Entities/SocketEntity.cs delete mode 100644 src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs delete mode 100644 src/Discord.Net.WebSocket/Entities/Users/Presence.cs delete mode 100644 src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs rename src/Discord.Net.WebSocket/Entities/Users/{VoiceState.cs => SocketVoiceState.cs} (74%) delete mode 100644 src/Discord.Net.WebSocket/Entities/Utilities/MessageManager.cs diff --git a/Discord.Net.sln b/Discord.Net.sln index a24b833e3..b29a7bca5 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -15,8 +15,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Dis EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Utils", "src\Discord.Net.Utils\Discord.Net.Utils.shproj", "{2B75119C-9893-4AAA-8D38-6176EEB09060}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" @@ -24,9 +22,6 @@ EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - src\Discord.Net.Utils\Discord.Net.Utils.projitems*{2b75119c-9893-4aaa-8d38-6176eeb09060}*SharedItemsImports = 13 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU diff --git a/src/Discord.Net.Core/AssemblyInfo.cs b/src/Discord.Net.Core/AssemblyInfo.cs new file mode 100644 index 000000000..0c0f48a50 --- /dev/null +++ b/src/Discord.Net.Core/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Discord.Net.Rest")] +[assembly: InternalsVisibleTo("Discord.Net.Rpc")] +[assembly: InternalsVisibleTo("Discord.Net.WebSocket")] +[assembly: InternalsVisibleTo("Discord.Net.Commands")] +[assembly: InternalsVisibleTo("Discord.Net.Test")] \ No newline at end of file diff --git a/src/Discord.Net.Core/Entities/Guilds/Emoji.cs b/src/Discord.Net.Core/Entities/Guilds/Emoji.cs index fc8b64fa4..ea9d176df 100644 --- a/src/Discord.Net.Core/Entities/Guilds/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Guilds/Emoji.cs @@ -14,7 +14,7 @@ namespace Discord public bool RequireColons { get; } public IReadOnlyList RoleIds { get; } - public Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList roleIds) + private Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList roleIds) { Id = id; Name = name; @@ -22,7 +22,7 @@ namespace Discord RequireColons = requireColons; RoleIds = roleIds; } - public static Emoji Create(Model model) + internal static Emoji Create(Model model) { return new Emoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 4d4aa1ea8..8cc650ce6 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -7,12 +7,12 @@ namespace Discord public string Name { get; } public string Url { get; } - public EmbedProvider(string name, string url) + private EmbedProvider(string name, string url) { Name = name; Url = url; } - public static EmbedProvider Create(Model model) + internal static EmbedProvider Create(Model model) { return new EmbedProvider(model.Name, model.Url); } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 8e1a2a527..43a37548c 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -9,14 +9,14 @@ namespace Discord public int? Height { get; } public int? Width { get; } - public EmbedThumbnail(string url, string proxyUrl, int? height, int? width) + private EmbedThumbnail(string url, string proxyUrl, int? height, int? width) { Url = url; ProxyUrl = proxyUrl; Height = height; Width = width; } - public static EmbedThumbnail Create(Model model) + internal static EmbedThumbnail Create(Model model) { return new EmbedThumbnail(model.Url, model.ProxyUrl, model.Height.IsSpecified ? model.Height.Value : (int?)null, diff --git a/src/Discord.Net.Core/Entities/Users/Game.cs b/src/Discord.Net.Core/Entities/Users/Game.cs index 2a8645df3..5bed84ddb 100644 --- a/src/Discord.Net.Core/Entities/Users/Game.cs +++ b/src/Discord.Net.Core/Entities/Users/Game.cs @@ -16,9 +16,9 @@ namespace Discord StreamUrl = streamUrl; StreamType = type; } - public Game(string name) + private Game(string name) : this(name, null, StreamType.NotStreaming) { } - public static Game Create(Model model) + internal static Game Create(Model model) { return new Game(model.Name, model.StreamUrl.GetValueOrDefault(null), diff --git a/src/Discord.Net.Core/Entities/Users/UserStatus.cs b/src/Discord.Net.Core/Entities/Users/UserStatus.cs index d71ec3173..ea2c3680e 100644 --- a/src/Discord.Net.Core/Entities/Users/UserStatus.cs +++ b/src/Discord.Net.Core/Entities/Users/UserStatus.cs @@ -5,6 +5,8 @@ Unknown, Online, Idle, + DoNotDisturb, + Invisible, Offline } } diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index d31f0ad0a..010d1b0dc 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -27,9 +27,7 @@ public static string Sanitize(string text) { foreach (string unsafeChar in SensitiveCharacters) - { text = text.Replace(unsafeChar, $"\\{unsafeChar}"); - } return text; } } diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index accd9080c..79ca30634 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -26,14 +26,12 @@ namespace Discord Task GetGuildAsync(ulong id); Task> GetGuildsAsync(); - Task> GetGuildSummariesAsync(); Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); Task GetInviteAsync(string inviteId); Task GetUserAsync(ulong id); Task GetUserAsync(string username, string discriminator); - Task> QueryUsersAsync(string query, int limit); Task> GetVoiceRegionsAsync(); Task GetVoiceRegionAsync(string id); diff --git a/src/Discord.Net.Utils/Logging/LogManager.cs b/src/Discord.Net.Core/Logging/LogManager.cs similarity index 78% rename from src/Discord.Net.Utils/Logging/LogManager.cs rename to src/Discord.Net.Core/Logging/LogManager.cs index dfcc97d33..86b63f73f 100644 --- a/src/Discord.Net.Utils/Logging/LogManager.cs +++ b/src/Discord.Net.Core/Logging/LogManager.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using System.Threading.Tasks; namespace Discord.Logging @@ -6,6 +7,7 @@ namespace Discord.Logging internal class LogManager { public LogSeverity Level { get; } + public Logger ClientLogger { get; } public event Func Message { add { _messageEvent.Add(value); } remove { _messageEvent.Remove(value); } } private readonly AsyncEvent> _messageEvent = new AsyncEvent>(); @@ -13,6 +15,7 @@ namespace Discord.Logging public LogManager(LogSeverity minSeverity) { Level = minSeverity; + ClientLogger = new Logger(this, "Discord"); } public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) @@ -67,5 +70,21 @@ namespace Discord.Logging => LogAsync(LogSeverity.Debug, source, 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); + await ClientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false); + await ClientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false); + } + private static string ToArchString(Architecture arch) + { + switch (arch) + { + case Architecture.X64: return "x64"; + case Architecture.X86: return "x86"; + default: return arch.ToString(); + } + } } } diff --git a/src/Discord.Net.Utils/Logging/Logger.cs b/src/Discord.Net.Core/Logging/Logger.cs similarity index 100% rename from src/Discord.Net.Utils/Logging/Logger.cs rename to src/Discord.Net.Core/Logging/Logger.cs diff --git a/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs b/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs index d2c25d3b8..3b4e1c0cb 100644 --- a/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs @@ -19,6 +19,10 @@ namespace Discord.Net.Converters return UserStatus.Online; case "idle": return UserStatus.Idle; + case "dnd": + return UserStatus.DoNotDisturb; + case "invisible": + return UserStatus.Invisible; //Should never happen case "offline": return UserStatus.Offline; default: @@ -36,6 +40,12 @@ namespace Discord.Net.Converters case UserStatus.Idle: writer.WriteValue("idle"); break; + case UserStatus.DoNotDisturb: + writer.WriteValue("dnd"); + break; + case UserStatus.Invisible: + writer.WriteValue("invisible"); + break; case UserStatus.Offline: writer.WriteValue("offline"); break; diff --git a/src/Discord.Net.Utils/AsyncEvent.cs b/src/Discord.Net.Core/Utils/AsyncEvent.cs similarity index 100% rename from src/Discord.Net.Utils/AsyncEvent.cs rename to src/Discord.Net.Core/Utils/AsyncEvent.cs diff --git a/src/Discord.Net.Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs similarity index 100% rename from src/Discord.Net.Utils/ConcurrentHashSet.cs rename to src/Discord.Net.Core/Utils/ConcurrentHashSet.cs diff --git a/src/Discord.Net.Utils/DateTimeUtils.cs b/src/Discord.Net.Core/Utils/DateTimeUtils.cs similarity index 100% rename from src/Discord.Net.Utils/DateTimeUtils.cs rename to src/Discord.Net.Core/Utils/DateTimeUtils.cs diff --git a/src/Discord.Net.Utils/Extensions/CollectionExtensions.cs b/src/Discord.Net.Core/Utils/Extensions/CollectionExtensions.cs similarity index 100% rename from src/Discord.Net.Utils/Extensions/CollectionExtensions.cs rename to src/Discord.Net.Core/Utils/Extensions/CollectionExtensions.cs diff --git a/src/Discord.Net.Utils/Extensions/Permissions.cs b/src/Discord.Net.Core/Utils/Extensions/Permissions.cs similarity index 100% rename from src/Discord.Net.Utils/Extensions/Permissions.cs rename to src/Discord.Net.Core/Utils/Extensions/Permissions.cs diff --git a/src/Discord.Net.Utils/Extensions/TaskCompletionSourceExtensions.cs b/src/Discord.Net.Core/Utils/Extensions/TaskCompletionSourceExtensions.cs similarity index 100% rename from src/Discord.Net.Utils/Extensions/TaskCompletionSourceExtensions.cs rename to src/Discord.Net.Core/Utils/Extensions/TaskCompletionSourceExtensions.cs diff --git a/src/Discord.Net.Utils/MentionsHelper.cs b/src/Discord.Net.Core/Utils/MentionsHelper.cs similarity index 100% rename from src/Discord.Net.Utils/MentionsHelper.cs rename to src/Discord.Net.Core/Utils/MentionsHelper.cs diff --git a/src/Discord.Net.Utils/Paging/Page.cs b/src/Discord.Net.Core/Utils/Paging/Page.cs similarity index 100% rename from src/Discord.Net.Utils/Paging/Page.cs rename to src/Discord.Net.Core/Utils/Paging/Page.cs diff --git a/src/Discord.Net.Utils/Paging/PageInfo.cs b/src/Discord.Net.Core/Utils/Paging/PageInfo.cs similarity index 100% rename from src/Discord.Net.Utils/Paging/PageInfo.cs rename to src/Discord.Net.Core/Utils/Paging/PageInfo.cs diff --git a/src/Discord.Net.Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs similarity index 100% rename from src/Discord.Net.Utils/Paging/PagedEnumerator.cs rename to src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs diff --git a/src/Discord.Net.Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs similarity index 100% rename from src/Discord.Net.Utils/Permissions.cs rename to src/Discord.Net.Core/Utils/Permissions.cs diff --git a/src/Discord.Net.Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs similarity index 100% rename from src/Discord.Net.Utils/Preconditions.cs rename to src/Discord.Net.Core/Utils/Preconditions.cs diff --git a/src/Discord.Net.Rest/AssemblyInfo.cs b/src/Discord.Net.Rest/AssemblyInfo.cs new file mode 100644 index 000000000..ea53dbb51 --- /dev/null +++ b/src/Discord.Net.Rest/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Discord.Net.Rpc")] +[assembly: InternalsVisibleTo("Discord.Net.WebSocket")] +[assembly: InternalsVisibleTo("Discord.Net.Commands")] +[assembly: InternalsVisibleTo("Discord.Net.Test")] \ No newline at end of file diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs new file mode 100644 index 000000000..07868182d --- /dev/null +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -0,0 +1,118 @@ +using Discord.API.Rest; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + internal static class ClientHelper + { + //Applications + public static async Task GetApplicationInfoAsync(DiscordClient client) + { + var model = await client.ApiClient.GetMyApplicationAsync().ConfigureAwait(false); + return RestApplication.Create(client, model); + } + + public static async Task GetChannelAsync(DiscordClient client, + ulong id) + { + var model = await client.ApiClient.GetChannelAsync(id).ConfigureAwait(false); + if (model != null) + return RestChannel.Create(client, model); + return null; + } + public static async Task> GetPrivateChannelsAsync(DiscordClient client) + { + var models = await client.ApiClient.GetMyPrivateChannelsAsync().ConfigureAwait(false); + return models.Select(x => RestDMChannel.Create(client, x)).ToImmutableArray(); + } + + public static async Task> GetConnectionsAsync(DiscordClient client) + { + var models = await client.ApiClient.GetMyConnectionsAsync().ConfigureAwait(false); + return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); + } + + public static async Task GetInviteAsync(DiscordClient client, + string inviteId) + { + var model = await client.ApiClient.GetInviteAsync(inviteId).ConfigureAwait(false); + if (model != null) + return RestInvite.Create(client, model); + return null; + } + + public static async Task GetGuildAsync(DiscordClient client, + ulong id) + { + var model = await client.ApiClient.GetGuildAsync(id).ConfigureAwait(false); + if (model != null) + return RestGuild.Create(client, model); + return null; + } + public static async Task GetGuildEmbedAsync(DiscordClient client, + ulong id) + { + var model = await client.ApiClient.GetGuildEmbedAsync(id).ConfigureAwait(false); + if (model != null) + return RestGuildEmbed.Create(model); + return null; + } + public static async Task> GetGuildSummariesAsync(DiscordClient client) + { + var models = await client.ApiClient.GetMyGuildsAsync().ConfigureAwait(false); + return models.Select(x => RestUserGuild.Create(client, x)).ToImmutableArray(); + } + public static async Task> GetGuildsAsync(DiscordClient client) + { + var summaryModels = await client.ApiClient.GetMyGuildsAsync().ConfigureAwait(false); + var guilds = ImmutableArray.CreateBuilder(summaryModels.Count); + foreach (var summaryModel in summaryModels) + { + var guildModel = await client.ApiClient.GetGuildAsync(summaryModel.Id).ConfigureAwait(false); + if (guildModel != null) + guilds.Add(RestGuild.Create(client, guildModel)); + } + return guilds.ToImmutable(); + } + public static async Task CreateGuildAsync(DiscordClient client, + string name, IVoiceRegion region, Stream jpegIcon = null) + { + var args = new CreateGuildParams(name, region.Id); + var model = await client.ApiClient.CreateGuildAsync(args).ConfigureAwait(false); + return RestGuild.Create(client, model); + } + + public static async Task GetUserAsync(DiscordClient client, + ulong id) + { + var model = await client.ApiClient.GetUserAsync(id).ConfigureAwait(false); + if (model != null) + return RestUser.Create(client, model); + return null; + } + public static async Task GetUserAsync(DiscordClient client, + string username, string discriminator) + { + var model = await client.ApiClient.GetUserAsync(username, discriminator).ConfigureAwait(false); + if (model != null) + return RestUser.Create(client, model); + return null; + } + + public static async Task> GetVoiceRegionsAsync(DiscordClient client) + { + var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); + return models.Select(x => RestVoiceRegion.Create(client, x)).ToImmutableArray(); + } + public static async Task GetVoiceRegionAsync(DiscordClient client, + string id) + { + var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); + return models.Select(x => RestVoiceRegion.Create(client, x)).Where(x => x.Id == id).FirstOrDefault(); + } + } +} diff --git a/src/Discord.Net.Rest/DiscordClient.cs b/src/Discord.Net.Rest/DiscordClient.cs new file mode 100644 index 000000000..5bf0f2c89 --- /dev/null +++ b/src/Discord.Net.Rest/DiscordClient.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Discord.Logging; +using System.Collections.Immutable; + +namespace Discord.Rest +{ + public abstract class DiscordClient : IDiscordClient + { + public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } + private 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, _queueLogger; + internal readonly SemaphoreSlim _connectionLock; + private bool _isFirstLogin; + private bool _isDisposed; + + public API.DiscordRestApiClient ApiClient { get; } + internal LogManager LogManager { get; } + public LoginState LoginState { get; private set; } + public ISelfUser CurrentUser { get; protected set; } + + /// Creates a new REST-only discord client. + internal DiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) + { + ApiClient = client; + LogManager = new LogManager(config.LogLevel); + LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); + + _connectionLock = new SemaphoreSlim(1, 1); + _restLogger = LogManager.CreateLogger("Rest"); + _queueLogger = LogManager.CreateLogger("Queue"); + _isFirstLogin = true; + + ApiClient.RequestQueue.RateLimitTriggered += async (id, bucket, millis) => + { + await _queueLogger.WarningAsync($"Rate limit triggered (id = \"{id ?? "null"}\")").ConfigureAwait(false); + if (bucket == null && id != null) + await _queueLogger.WarningAsync($"Unknown rate limit bucket \"{id ?? "null"}\"").ConfigureAwait(false); + }; + ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); + } + + /// + public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await LoginInternalAsync(tokenType, token).ConfigureAwait(false); + } + finally { _connectionLock.Release(); } + } + private async Task LoginInternalAsync(TokenType tokenType, string token) + { + if (_isFirstLogin) + { + _isFirstLogin = false; + await LogManager.WriteInitialLog().ConfigureAwait(false); + } + + if (LoginState != LoginState.LoggedOut) + await LogoutInternalAsync().ConfigureAwait(false); + LoginState = LoginState.LoggingIn; + + try + { + await OnLoginAsync(tokenType, token).ConfigureAwait(false); + LoginState = LoginState.LoggedIn; + } + catch (Exception) + { + await LogoutInternalAsync().ConfigureAwait(false); + throw; + } + + await _loggedInEvent.InvokeAsync().ConfigureAwait(false); + } + protected virtual Task OnLoginAsync(TokenType tokenType, string token) { return Task.CompletedTask; } + + /// + public async Task LogoutAsync() + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await LogoutInternalAsync().ConfigureAwait(false); + } + finally { _connectionLock.Release(); } + } + private async Task LogoutInternalAsync() + { + if (LoginState == LoginState.LoggedOut) return; + LoginState = LoginState.LoggingOut; + + await ApiClient.LogoutAsync().ConfigureAwait(false); + + await OnLogoutAsync().ConfigureAwait(false); + CurrentUser = null; + LoginState = LoginState.LoggedOut; + + await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); + } + protected virtual Task OnLogoutAsync() { return Task.CompletedTask; } + + internal virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + ApiClient.Dispose(); + _isDisposed = true; + } + } + /// + public void Dispose() => Dispose(true); + + //IDiscordClient + ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; + ISelfUser IDiscordClient.CurrentUser => CurrentUser; + + Task IDiscordClient.GetApplicationInfoAsync() { throw new NotSupportedException(); } + + Task IDiscordClient.GetChannelAsync(ulong id) + => Task.FromResult(null); + Task> IDiscordClient.GetPrivateChannelsAsync() + => Task.FromResult>(ImmutableArray.Create()); + + Task> IDiscordClient.GetConnectionsAsync() + => Task.FromResult>(ImmutableArray.Create()); + + Task IDiscordClient.GetInviteAsync(string inviteId) + => Task.FromResult(null); + + Task IDiscordClient.GetGuildAsync(ulong id) + => Task.FromResult(null); + Task> IDiscordClient.GetGuildsAsync() + => Task.FromResult>(ImmutableArray.Create()); + Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) { throw new NotSupportedException(); } + + Task IDiscordClient.GetUserAsync(ulong id) + => Task.FromResult(null); + Task IDiscordClient.GetUserAsync(string username, string discriminator) + => Task.FromResult(null); + + Task> IDiscordClient.GetVoiceRegionsAsync() + => Task.FromResult>(ImmutableArray.Create()); + Task IDiscordClient.GetVoiceRegionAsync(string id) + => Task.FromResult(null); + + Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); } + Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); } + + } +} diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index b1668250d..8bc3ca9c3 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -1,324 +1,105 @@ -using Discord.API.Rest; -using Discord.Net; -using Discord.Net.Queue; -using System; +using Discord.Net.Queue; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; -using System.Linq; -using System.Threading; using System.Threading.Tasks; -using System.Runtime.InteropServices; -using Discord.Logging; namespace Discord.Rest { - public class DiscordRestClient : IDiscordClient + public class DiscordRestClient : DiscordClient, IDiscordClient { - private readonly object _eventLock = new object(); + public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; - public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } - private 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 _clientLogger, _restLogger, _queueLogger; - internal readonly SemaphoreSlim _connectionLock; - private bool _isFirstLogSub; - internal bool _isDisposed; - - public API.DiscordRestApiClient ApiClient { get; } - internal LogManager LogManager { get; } - public LoginState LoginState { get; private set; } - public RestSelfUser CurrentUser { get; private set; } - - /// Creates a new REST-only discord client. public DiscordRestClient() : this(new DiscordRestConfig()) { } - public DiscordRestClient(DiscordRestConfig config) : this(config, CreateApiClient(config)) { } - /// Creates a new REST-only discord client. - internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient client) - { - ApiClient = client; - LogManager = new LogManager(config.LogLevel); - LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); - _clientLogger = LogManager.CreateLogger("Client"); - _restLogger = LogManager.CreateLogger("Rest"); - _queueLogger = LogManager.CreateLogger("Queue"); - _isFirstLogSub = true; + public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { } - _connectionLock = new SemaphoreSlim(1, 1); - - ApiClient.RequestQueue.RateLimitTriggered += async (id, bucket, millis) => - { - await _queueLogger.WarningAsync($"Rate limit triggered (id = \"{id ?? "null"}\")").ConfigureAwait(false); - if (bucket == null && id != null) - await _queueLogger.WarningAsync($"Unknown rate limit bucket \"{id ?? "null"}\"").ConfigureAwait(false); - }; - ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); - } private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, requestQueue: new RequestQueue()); - /// - public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) - { - await _connectionLock.WaitAsync().ConfigureAwait(false); - try - { - await LoginInternalAsync(tokenType, token).ConfigureAwait(false); - } - finally { _connectionLock.Release(); } - } - private async Task LoginInternalAsync(TokenType tokenType, string token) - { - if (_isFirstLogSub) - { - _isFirstLogSub = false; - await WriteInitialLog().ConfigureAwait(false); - } - - if (LoginState != LoginState.LoggedOut) - await LogoutInternalAsync().ConfigureAwait(false); - LoginState = LoginState.LoggingIn; - - try - { - await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); - CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); - - await OnLoginAsync(tokenType, token).ConfigureAwait(false); - LoginState = LoginState.LoggedIn; - } - catch (Exception) - { - await LogoutInternalAsync().ConfigureAwait(false); - throw; - } - - await _loggedInEvent.InvokeAsync().ConfigureAwait(false); - } - protected virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.CompletedTask; - - /// - public async Task LogoutAsync() + protected override async Task OnLoginAsync(TokenType tokenType, string token) { - await _connectionLock.WaitAsync().ConfigureAwait(false); - try - { - await LogoutInternalAsync().ConfigureAwait(false); - } - finally { _connectionLock.Release(); } + await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); + base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); } - private async Task LogoutInternalAsync() - { - if (LoginState == LoginState.LoggedOut) return; - LoginState = LoginState.LoggingOut; - - await ApiClient.LogoutAsync().ConfigureAwait(false); - await OnLogoutAsync().ConfigureAwait(false); - - CurrentUser = null; - LoginState = LoginState.LoggedOut; - - await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); - } - protected virtual Task OnLogoutAsync() => Task.CompletedTask; /// - public async Task GetApplicationInfoAsync() - { - var model = await ApiClient.GetMyApplicationAsync().ConfigureAwait(false); - return RestApplication.Create(this, model); - } + public Task GetApplicationInfoAsync() + => ClientHelper.GetApplicationInfoAsync(this); /// - public virtual async Task GetChannelAsync(ulong id) - { - var model = await ApiClient.GetChannelAsync(id).ConfigureAwait(false); - if (model != null) - { - switch (model.Type) - { - case ChannelType.Text: - return RestTextChannel.Create(this, model); - case ChannelType.Voice: - return RestVoiceChannel.Create(this, model); - case ChannelType.DM: - return RestDMChannel.Create(this, model); - case ChannelType.Group: - return RestGroupChannel.Create(this, model); - default: - throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); - } - } - return null; - } + public Task GetChannelAsync(ulong id) + => ClientHelper.GetChannelAsync(this, id); /// - public virtual async Task> GetPrivateChannelsAsync() - { - var models = await ApiClient.GetMyPrivateChannelsAsync().ConfigureAwait(false); - return models.Select(x => RestDMChannel.Create(this, x)).ToImmutableArray(); - } - + public Task> GetPrivateChannelsAsync() + => ClientHelper.GetPrivateChannelsAsync(this); + /// - public async Task> GetConnectionsAsync() - { - var models = await ApiClient.GetMyConnectionsAsync().ConfigureAwait(false); - return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); - } + public Task> GetConnectionsAsync() + => ClientHelper.GetConnectionsAsync(this); /// - public virtual async Task GetInviteAsync(string inviteId) - { - var model = await ApiClient.GetInviteAsync(inviteId).ConfigureAwait(false); - if (model != null) - return RestInvite.Create(this, model); - return null; - } + public Task GetInviteAsync(string inviteId) + => ClientHelper.GetInviteAsync(this, inviteId); /// - public virtual async Task GetGuildAsync(ulong id) - { - var model = await ApiClient.GetGuildAsync(id).ConfigureAwait(false); - if (model != null) - return RestGuild.Create(this, model); - return null; - } + public Task GetGuildAsync(ulong id) + => ClientHelper.GetGuildAsync(this, id); /// - public virtual async Task GetGuildEmbedAsync(ulong id) - { - var model = await ApiClient.GetGuildEmbedAsync(id).ConfigureAwait(false); - if (model != null) - return RestGuildEmbed.Create(model); - return null; - } + public Task GetGuildEmbedAsync(ulong id) + => ClientHelper.GetGuildEmbedAsync(this, id); /// - public virtual async Task> GetGuildSummariesAsync() - { - var models = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false); - return models.Select(x => RestUserGuild.Create(this, x)).ToImmutableArray(); - } + public Task> GetGuildSummariesAsync() + => ClientHelper.GetGuildSummariesAsync(this); /// - public virtual async Task> GetGuildsAsync() - { - var summaryModels = await ApiClient.GetMyGuildsAsync().ConfigureAwait(false); - var guilds = ImmutableArray.CreateBuilder(summaryModels.Count); - foreach (var summaryModel in summaryModels) - { - var guildModel = await ApiClient.GetGuildAsync(summaryModel.Id).ConfigureAwait(false); - if (guildModel != null) - guilds.Add(RestGuild.Create(this, guildModel)); - } - return guilds.ToImmutable(); - } + public Task> GetGuildsAsync() + => ClientHelper.GetGuildsAsync(this); /// - public virtual async Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) - { - var args = new CreateGuildParams(name, region.Id); - var model = await ApiClient.CreateGuildAsync(args).ConfigureAwait(false); - return RestGuild.Create(this, model); - } + public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) + => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon); /// - public virtual async Task GetUserAsync(ulong id) - { - var model = await ApiClient.GetUserAsync(id).ConfigureAwait(false); - if (model != null) - return RestUser.Create(this, model); - return null; - } + public Task GetUserAsync(ulong id) + => ClientHelper.GetUserAsync(this, id); /// - public virtual async Task GetUserAsync(string username, string discriminator) - { - var model = await ApiClient.GetUserAsync(username, discriminator).ConfigureAwait(false); - if (model != null) - return RestUser.Create(this, model); - return null; - } + public Task GetUserAsync(string username, string discriminator) + => ClientHelper.GetUserAsync(this, username, discriminator); /// - public virtual async Task> QueryUsersAsync(string query, int limit) - { - var models = await ApiClient.QueryUsersAsync(query, limit).ConfigureAwait(false); - return models.Select(x => RestUser.Create(this, x)).ToImmutableArray(); - } - - /// - public virtual async Task> GetVoiceRegionsAsync() - { - var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); - return models.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableArray(); - } - /// - public virtual async Task GetVoiceRegionAsync(string id) - { - var models = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); - return models.Select(x => RestVoiceRegion.Create(this, x)).Where(x => x.Id == id).FirstOrDefault(); - } - - internal virtual void Dispose(bool disposing) - { - if (!_isDisposed) - { - ApiClient.Dispose(); - _isDisposed = true; - } - } + public Task> GetVoiceRegionsAsync() + => ClientHelper.GetVoiceRegionsAsync(this); /// - public void Dispose() => Dispose(true); - - private async Task WriteInitialLog() - { - /*if (this is DiscordSocketClient) - await _clientLogger.InfoAsync($"DiscordSocketClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, {DiscordSocketConfig.GatewayEncoding})").ConfigureAwait(false); - else if (this is DiscordRpcClient) - await _clientLogger.InfoAsync($"DiscordRpcClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, RPC API v{DiscordRpcConfig.RpcAPIVersion})").ConfigureAwait(false);*/ - await _clientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); - await _clientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false); - await _clientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false); - await _clientLogger.VerboseAsync($"Processors: {Environment.ProcessorCount}").ConfigureAwait(false); - } - private static string ToArchString(Architecture arch) - { - switch (arch) - { - case Architecture.X64: return "x64"; - case Architecture.X86: return "x86"; - default: return arch.ToString(); - } - } + public Task GetVoiceRegionAsync(string id) + => ClientHelper.GetVoiceRegionAsync(this, id); //IDiscordClient - ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; - ISelfUser IDiscordClient.CurrentUser => CurrentUser; + async Task IDiscordClient.GetApplicationInfoAsync() + => await GetApplicationInfoAsync().ConfigureAwait(false); + + async Task IDiscordClient.GetChannelAsync(ulong id) + => await GetChannelAsync(id); + async Task> IDiscordClient.GetPrivateChannelsAsync() + => await GetPrivateChannelsAsync(); async Task> IDiscordClient.GetConnectionsAsync() => await GetConnectionsAsync().ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId) => await GetInviteAsync(inviteId).ConfigureAwait(false); + async Task IDiscordClient.GetGuildAsync(ulong id) => await GetGuildAsync(id).ConfigureAwait(false); - async Task> IDiscordClient.GetGuildSummariesAsync() - => await GetGuildSummariesAsync().ConfigureAwait(false); async Task> IDiscordClient.GetGuildsAsync() => await GetGuildsAsync().ConfigureAwait(false); async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); + async Task IDiscordClient.GetUserAsync(ulong id) => await GetUserAsync(id).ConfigureAwait(false); async Task IDiscordClient.GetUserAsync(string username, string discriminator) => await GetUserAsync(username, discriminator).ConfigureAwait(false); - async Task> IDiscordClient.QueryUsersAsync(string query, int limit) - => await QueryUsersAsync(query, limit).ConfigureAwait(false); + async Task> IDiscordClient.GetVoiceRegionsAsync() => await GetVoiceRegionsAsync().ConfigureAwait(false); async Task IDiscordClient.GetVoiceRegionAsync(string id) => await GetVoiceRegionAsync(id).ConfigureAwait(false); - - Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); } - Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 26512acaf..63f68049f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -12,33 +12,33 @@ namespace Discord.Rest internal static class ChannelHelper { //General - public static async Task GetAsync(IGuildChannel channel, DiscordRestClient client) + public static async Task GetAsync(IGuildChannel channel, DiscordClient client) { return await client.ApiClient.GetChannelAsync(channel.GuildId, channel.Id).ConfigureAwait(false); } - public static async Task GetAsync(IPrivateChannel channel, DiscordRestClient client) + public static async Task GetAsync(IPrivateChannel channel, DiscordClient client) { return await client.ApiClient.GetChannelAsync(channel.Id).ConfigureAwait(false); } - public static async Task DeleteAsync(IChannel channel, DiscordRestClient client) + public static async Task DeleteAsync(IChannel channel, DiscordClient client) { await client.ApiClient.DeleteChannelAsync(channel.Id).ConfigureAwait(false); } - public static async Task ModifyAsync(IGuildChannel channel, DiscordRestClient client, + public static async Task ModifyAsync(IGuildChannel channel, DiscordClient client, Action func) { var args = new ModifyGuildChannelParams(); func(args); await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); } - public static async Task ModifyAsync(ITextChannel channel, DiscordRestClient client, + public static async Task ModifyAsync(ITextChannel channel, DiscordClient client, Action func) { var args = new ModifyTextChannelParams(); func(args); await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); } - public static async Task ModifyAsync(IVoiceChannel channel, DiscordRestClient client, + public static async Task ModifyAsync(IVoiceChannel channel, DiscordClient client, Action func) { var args = new ModifyVoiceChannelParams(); @@ -47,12 +47,12 @@ namespace Discord.Rest } //Invites - public static async Task> GetInvitesAsync(IChannel channel, DiscordRestClient client) + public static async Task> GetInvitesAsync(IChannel channel, DiscordClient client) { var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); } - public static async Task CreateInviteAsync(IChannel channel, DiscordRestClient client, + public static async Task CreateInviteAsync(IChannel channel, DiscordClient client, int? maxAge, int? maxUses, bool isTemporary) { var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; @@ -65,13 +65,13 @@ namespace Discord.Rest } //Messages - public static async Task GetMessageAsync(IChannel channel, DiscordRestClient client, + public static async Task GetMessageAsync(IChannel channel, DiscordClient client, ulong id) { var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false); return RestMessage.Create(client, model); } - public static PagedAsyncEnumerable GetMessagesAsync(IChannel channel, DiscordRestClient client, + public static IAsyncEnumerable> GetMessagesAsync(IChannel channel, DiscordClient client, ulong? fromMessageId = null, Direction dir = Direction.Before, int limit = DiscordConfig.MaxMessagesPerBatch) { //TODO: Test this with Around direction @@ -102,13 +102,13 @@ namespace Discord.Rest count: (uint)limit ); } - public static async Task> GetPinnedMessagesAsync(IChannel channel, DiscordRestClient client) + public static async Task> GetPinnedMessagesAsync(IChannel channel, DiscordClient client) { var models = await client.ApiClient.GetPinsAsync(channel.Id).ConfigureAwait(false); return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); } - public static async Task SendMessageAsync(IChannel channel, DiscordRestClient client, + public static async Task SendMessageAsync(IChannel channel, DiscordClient client, string text, bool isTTS) { var args = new CreateMessageParams(text) { IsTTS = isTTS }; @@ -116,14 +116,14 @@ namespace Discord.Rest return RestUserMessage.Create(client, model); } - public static Task SendFileAsync(IChannel channel, DiscordRestClient client, + public static Task SendFileAsync(IChannel channel, DiscordClient client, string filePath, string text, bool isTTS) { string filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) return SendFileAsync(channel, client, file, filename, text, isTTS); } - public static async Task SendFileAsync(IChannel channel, DiscordRestClient client, + public static async Task SendFileAsync(IChannel channel, DiscordClient client, Stream stream, string filename, string text, bool isTTS) { var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; @@ -131,7 +131,7 @@ namespace Discord.Rest return RestUserMessage.Create(client, model); } - public static async Task DeleteMessagesAsync(IChannel channel, DiscordRestClient client, + public static async Task DeleteMessagesAsync(IChannel channel, DiscordClient client, IEnumerable messages) { var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); @@ -139,31 +139,31 @@ namespace Discord.Rest } //Permission Overwrites - public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, + public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, IUser user, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args).ConfigureAwait(false); } - public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, + public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, IRole role, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args).ConfigureAwait(false); } - public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, + public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, IUser user) { await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id).ConfigureAwait(false); } - public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordRestClient client, + public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, IRole role) { await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false); } //Users - public static async Task GetUserAsync(IGuildChannel channel, DiscordRestClient client, + public static async Task GetUserAsync(IGuildChannel channel, DiscordClient client, ulong id) { var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id); @@ -175,7 +175,7 @@ namespace Discord.Rest return user; } - public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, DiscordRestClient client, + public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, DiscordClient client, ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch) { return new PagedAsyncEnumerable( @@ -203,7 +203,7 @@ namespace Discord.Rest } //Typing - public static IDisposable EnterTypingState(IChannel channel, DiscordRestClient client) + public static IDisposable EnterTypingState(IChannel channel, DiscordClient client) { throw new NotImplementedException(); //TODO: Impl } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs new file mode 100644 index 000000000..ada4008ec --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public abstract class RestChannel : RestEntity, IChannel, IUpdateable + { + internal RestChannel(DiscordClient discord, ulong id) + : base(discord, id) + { + } + internal static RestChannel Create(DiscordClient discord, Model model) + { + switch (model.Type) + { + case ChannelType.Text: + return RestTextChannel.Create(discord, model); + case ChannelType.Voice: + return RestVoiceChannel.Create(discord, model); + case ChannelType.DM: + return RestDMChannel.Create(discord, model); + case ChannelType.Group: + return RestGroupChannel.Create(discord, model); + default: + throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); + } + } + internal abstract void Update(Model model); + + public abstract Task UpdateAsync(); + + //IChannel + IReadOnlyCollection IChannel.CachedUsers => ImmutableArray.Create(); + + IUser IChannel.GetCachedUser(ulong id) + => null; + Task IChannel.GetUserAsync(ulong id) + => Task.FromResult(null); + IAsyncEnumerable> IChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 796ec23cb..973a001a8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -10,28 +10,31 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestDMChannel : RestEntity, IDMChannel, IUpdateable + public class RestDMChannel : RestChannel, IDMChannel, IUpdateable { - public RestUser Recipient { get; } + public RestUser CurrentUser { get; private set; } + public RestUser Recipient { get; private set; } - public IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); + public IReadOnlyCollection Users => ImmutableArray.Create(CurrentUser, Recipient); - internal RestDMChannel(DiscordRestClient discord, ulong id) + internal RestDMChannel(DiscordClient discord, ulong id, ulong recipientId) : base(discord, id) { + Recipient = new RestUser(Discord, recipientId); + CurrentUser = new RestUser(Discord, discord.CurrentUser.Id); } - internal static RestDMChannel Create(DiscordRestClient discord, Model model) + internal new static RestDMChannel Create(DiscordClient discord, Model model) { - var entity = new RestDMChannel(discord, model.Id); + var entity = new RestDMChannel(discord, model.Id, model.Recipients.Value[0].Id); entity.Update(model); return entity; } - internal void Update(Model model) + internal override void Update(Model model) { Recipient.Update(model.Recipients.Value[0]); } - public async Task UpdateAsync() + public override async Task UpdateAsync() => Update(await ChannelHelper.GetAsync(this, Discord)); public Task CloseAsync() => ChannelHelper.DeleteAsync(this, Discord); @@ -41,7 +44,7 @@ namespace Discord.Rest if (id == Recipient.Id) return Recipient; else if (id == Discord.CurrentUser.Id) - return Discord.CurrentUser; + return CurrentUser; else return null; } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index b95cfce14..00be0ca11 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGroupChannel : RestEntity, IGroupChannel, IUpdateable + public class RestGroupChannel : RestChannel, IGroupChannel, IUpdateable { private string _iconId; private ImmutableDictionary _users; @@ -21,17 +21,17 @@ namespace Discord.Rest public IReadOnlyCollection Recipients => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); - internal RestGroupChannel(DiscordRestClient discord, ulong id) + internal RestGroupChannel(DiscordClient discord, ulong id) : base(discord, id) { } - internal static RestGroupChannel Create(DiscordRestClient discord, Model model) + internal new static RestGroupChannel Create(DiscordClient discord, Model model) { var entity = new RestGroupChannel(discord, model.Id); entity.Update(model); return entity; } - internal void Update(Model model) + internal override void Update(Model model) { if (model.Name.IsSpecified) Name = model.Name.Value; @@ -49,7 +49,7 @@ namespace Discord.Rest _users = users.ToImmutable(); } - public async Task UpdateAsync() + public override async Task UpdateAsync() => Update(await ChannelHelper.GetAsync(this, Discord)); public Task LeaveAsync() => ChannelHelper.DeleteAsync(this, Discord); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 62422efe1..4ec79496d 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public abstract class RestGuildChannel : RestEntity, IGuildChannel, IUpdateable + public abstract class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable { private ImmutableArray _overwrites; @@ -21,12 +21,12 @@ namespace Discord.Rest public string Name { get; private set; } public int Position { get; private set; } - internal RestGuildChannel(DiscordRestClient discord, ulong id, ulong guildId) + internal RestGuildChannel(DiscordClient discord, ulong id, ulong guildId) : base(discord, id) { GuildId = guildId; } - internal static RestGuildChannel Create(DiscordRestClient discord, Model model) + internal new static RestGuildChannel Create(DiscordClient discord, Model model) { switch (model.Type) { @@ -38,7 +38,7 @@ namespace Discord.Rest throw new InvalidOperationException("Unknown guild channel type"); } } - internal virtual void Update(Model model) + internal override void Update(Model model) { Name = model.Name.Value; Position = model.Position.Value; @@ -50,7 +50,7 @@ namespace Discord.Rest _overwrites = newOverwrites.ToImmutable(); } - public async Task UpdateAsync() + public override async Task UpdateAsync() => Update(await ChannelHelper.GetAsync(this, Discord)); public Task ModifyAsync(Action func) => ChannelHelper.ModifyAsync(this, Discord, func); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 65877868c..69917f862 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -16,11 +16,11 @@ namespace Discord.Rest public string Mention => MentionUtils.MentionChannel(Id); - internal RestTextChannel(DiscordRestClient discord, ulong id, ulong guildId) + internal RestTextChannel(DiscordClient discord, ulong id, ulong guildId) : base(discord, id, guildId) { } - internal new static RestTextChannel Create(DiscordRestClient discord, Model model) + internal new static RestTextChannel Create(DiscordClient discord, Model model) { var entity = new RestTextChannel(discord, model.Id, model.GuildId.Value); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index a3187ff12..2b23eaad7 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -16,11 +16,11 @@ namespace Discord.Rest public int Bitrate { get; private set; } public int UserLimit { get; private set; } - internal RestVoiceChannel(DiscordRestClient discord, ulong id, ulong guildId) + internal RestVoiceChannel(DiscordClient discord, ulong id, ulong guildId) : base(discord, id, guildId) { } - internal new static RestVoiceChannel Create(DiscordRestClient discord, Model model) + internal new static RestVoiceChannel Create(DiscordClient discord, Model model) { var entity = new RestVoiceChannel(discord, model.Id, model.GuildId.Value); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 02fb76868..e88c38504 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -13,7 +13,7 @@ namespace Discord.Rest internal static class GuildHelper { //General - public static async Task ModifyAsync(IGuild guild, DiscordRestClient client, + public static async Task ModifyAsync(IGuild guild, DiscordClient client, Action func) { if (func == null) throw new NullReferenceException(nameof(func)); @@ -28,7 +28,7 @@ namespace Discord.Rest return await client.ApiClient.ModifyGuildAsync(guild.Id, args).ConfigureAwait(false); } - public static async Task ModifyEmbedAsync(IGuild guild, DiscordRestClient client, + public static async Task ModifyEmbedAsync(IGuild guild, DiscordClient client, Action func) { if (func == null) throw new NullReferenceException(nameof(func)); @@ -37,46 +37,46 @@ namespace Discord.Rest func(args); return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args).ConfigureAwait(false); } - public static async Task ModifyChannelsAsync(IGuild guild, DiscordRestClient client, + public static async Task ModifyChannelsAsync(IGuild guild, DiscordClient client, IEnumerable args) { await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args).ConfigureAwait(false); } - public static async Task> ModifyRolesAsync(IGuild guild, DiscordRestClient client, + public static async Task> ModifyRolesAsync(IGuild guild, DiscordClient client, IEnumerable args) { return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args).ConfigureAwait(false); } - public static async Task LeaveAsync(IGuild guild, DiscordRestClient client) + public static async Task LeaveAsync(IGuild guild, DiscordClient client) { await client.ApiClient.LeaveGuildAsync(guild.Id).ConfigureAwait(false); } - public static async Task DeleteAsync(IGuild guild, DiscordRestClient client) + public static async Task DeleteAsync(IGuild guild, DiscordClient client) { await client.ApiClient.DeleteGuildAsync(guild.Id).ConfigureAwait(false); } //Bans - public static async Task> GetBansAsync(IGuild guild, DiscordRestClient client) + public static async Task> GetBansAsync(IGuild guild, DiscordClient client) { var models = await client.ApiClient.GetGuildBansAsync(guild.Id); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); } - public static async Task AddBanAsync(IGuild guild, DiscordRestClient client, + public static async Task AddBanAsync(IGuild guild, DiscordClient client, ulong userId, int pruneDays) { var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args); } - public static async Task RemoveBanAsync(IGuild guild, DiscordRestClient client, + public static async Task RemoveBanAsync(IGuild guild, DiscordClient client, ulong userId) { await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId); } //Channels - public static async Task GetChannelAsync(IGuild guild, DiscordRestClient client, + public static async Task GetChannelAsync(IGuild guild, DiscordClient client, ulong id) { var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false); @@ -84,12 +84,12 @@ namespace Discord.Rest return RestGuildChannel.Create(client, model); return null; } - public static async Task> GetChannelsAsync(IGuild guild, DiscordRestClient client) + public static async Task> GetChannelsAsync(IGuild guild, DiscordClient client) { var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); return models.Select(x => RestGuildChannel.Create(client, x)).ToImmutableArray(); } - public static async Task CreateTextChannelAsync(IGuild guild, DiscordRestClient client, + public static async Task CreateTextChannelAsync(IGuild guild, DiscordClient client, string name) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -98,7 +98,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); return RestTextChannel.Create(client, model); } - public static async Task CreateVoiceChannelAsync(IGuild guild, DiscordRestClient client, + public static async Task CreateVoiceChannelAsync(IGuild guild, DiscordClient client, string name) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -109,12 +109,12 @@ namespace Discord.Rest } //Integrations - public static async Task> GetIntegrationsAsync(IGuild guild, DiscordRestClient client) + public static async Task> GetIntegrationsAsync(IGuild guild, DiscordClient client) { var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id).ConfigureAwait(false); return models.Select(x => RestGuildIntegration.Create(client, x)).ToImmutableArray(); } - public static async Task CreateIntegrationAsync(IGuild guild, DiscordRestClient client, + public static async Task CreateIntegrationAsync(IGuild guild, DiscordClient client, ulong id, string type) { var args = new CreateGuildIntegrationParams(id, type); @@ -123,14 +123,14 @@ namespace Discord.Rest } //Invites - public static async Task> GetInvitesAsync(IGuild guild, DiscordRestClient client) + public static async Task> GetInvitesAsync(IGuild guild, DiscordClient client) { var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id).ConfigureAwait(false); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); } //Roles - public static async Task CreateRoleAsync(IGuild guild, DiscordRestClient client, + public static async Task CreateRoleAsync(IGuild guild, DiscordClient client, string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -150,7 +150,7 @@ namespace Discord.Rest } //Users - public static async Task GetUserAsync(IGuild guild, DiscordRestClient client, + public static async Task GetUserAsync(IGuild guild, DiscordClient client, ulong id) { var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false); @@ -158,17 +158,17 @@ namespace Discord.Rest return RestGuildUser.Create(client, model); return null; } - public static async Task GetCurrentUserAsync(IGuild guild, DiscordRestClient client) + public static async Task GetCurrentUserAsync(IGuild guild, DiscordClient client) { return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false); } - public static async Task> GetUsersAsync(IGuild guild, DiscordRestClient client) + public static async Task> GetUsersAsync(IGuild guild, DiscordClient client) { var args = new GetGuildMembersParams(); var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false); return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); } - public static async Task PruneUsersAsync(IGuild guild, DiscordRestClient client, + public static async Task PruneUsersAsync(IGuild guild, DiscordClient client, int days = 30, bool simulate = false) { var args = new GuildPruneParams(days); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs index 8e2b65576..0d919bf0f 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs @@ -14,7 +14,7 @@ namespace Discord.Rest User = user; Reason = reason; } - internal static RestBan Create(DiscordRestClient client, Model model) + internal static RestBan Create(DiscordClient client, Model model) { return new RestBan(RestUser.Create(client, model.User), model.Reason); } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 4735debf9..48ddfa607 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -39,11 +39,11 @@ namespace Discord.Rest public IReadOnlyCollection Emojis => _emojis; public IReadOnlyCollection Features => _features; - internal RestGuild(DiscordRestClient client, ulong id) + internal RestGuild(DiscordClient client, ulong id) : base(client, id) { } - internal static RestGuild Create(DiscordRestClient discord, Model model) + internal static RestGuild Create(DiscordClient discord, Model model) { var entity = new RestGuild(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs index bbbe57a0d..bb40a4da4 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs @@ -25,11 +25,11 @@ namespace Discord.Rest public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); - internal RestGuildIntegration(DiscordRestClient discord, ulong id) + internal RestGuildIntegration(DiscordClient discord, ulong id) : base(discord, id) { } - internal static RestGuildIntegration Create(DiscordRestClient discord, Model model) + internal static RestGuildIntegration Create(DiscordClient discord, Model model) { var entity = new RestGuildIntegration(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index e1f51b4a9..fab07ee33 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -15,11 +15,11 @@ namespace Discord.Rest public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); - internal RestUserGuild(DiscordRestClient discord, ulong id) + internal RestUserGuild(DiscordClient discord, ulong id) : base(discord, id) { } - internal static RestUserGuild Create(DiscordRestClient discord, Model model) + internal static RestUserGuild Create(DiscordClient discord, Model model) { var entity = new RestUserGuild(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs index b6e4c2a26..465b2e185 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs @@ -13,11 +13,11 @@ namespace Discord public string SampleHostname { get; private set; } public int SamplePort { get; private set; } - internal RestVoiceRegion(DiscordRestClient client, string id) + internal RestVoiceRegion(DiscordClient client, string id) : base(client, id) { } - internal static RestVoiceRegion Create(DiscordRestClient client, Model model) + internal static RestVoiceRegion Create(DiscordClient client, Model model) { var entity = new RestVoiceRegion(client, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs index 40bbe3632..3ede5d042 100644 --- a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs +++ b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs @@ -5,15 +5,15 @@ namespace Discord.Rest { internal static class InviteHelper { - public static async Task GetAsync(IInvite invite, DiscordRestClient client) + public static async Task GetAsync(IInvite invite, DiscordClient client) { return await client.ApiClient.GetInviteAsync(invite.Code).ConfigureAwait(false); } - public static async Task AcceptAsync(IInvite invite, DiscordRestClient client) + public static async Task AcceptAsync(IInvite invite, DiscordClient client) { await client.ApiClient.AcceptInviteAsync(invite.Code).ConfigureAwait(false); } - public static async Task DeleteAsync(IInvite invite, DiscordRestClient client) + public static async Task DeleteAsync(IInvite invite, DiscordClient client) { await client.ApiClient.DeleteInviteAsync(invite.Code).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 84af7e029..3cf11b4c6 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -16,11 +16,11 @@ namespace Discord.Rest public string Code => Id; public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; - internal RestInvite(DiscordRestClient discord, string id) + internal RestInvite(DiscordClient discord, string id) : base(discord, id) { } - internal static RestInvite Create(DiscordRestClient discord, Model model) + internal static RestInvite Create(DiscordClient discord, Model model) { var entity = new RestInvite(discord, model.Code); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 138fd6f66..25ce2fb24 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -18,11 +18,11 @@ namespace Discord.Rest public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); - internal RestInviteMetadata(DiscordRestClient discord, string id) + internal RestInviteMetadata(DiscordClient discord, string id) : base(discord, id) { } - internal static RestInviteMetadata Create(DiscordRestClient discord, Model model) + internal static RestInviteMetadata Create(DiscordClient discord, Model model) { var entity = new RestInviteMetadata(discord, model.Code); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 54f0982b1..f1f0e8461 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -6,26 +6,26 @@ namespace Discord.Rest { internal static class MessageHelper { - public static async Task GetAsync(IMessage msg, DiscordRestClient client) + public static async Task GetAsync(IMessage msg, DiscordClient client) { await client.ApiClient.GetChannelMessageAsync(msg.ChannelId, msg.Id); } - public static async Task ModifyAsync(IMessage msg, DiscordRestClient client, Action func) + public static async Task ModifyAsync(IMessage msg, DiscordClient client, Action func) { var args = new ModifyMessageParams(); func(args); await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args); } - public static async Task DeleteAsync(IMessage msg, DiscordRestClient client) + public static async Task DeleteAsync(IMessage msg, DiscordClient client) { await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id); } - public static async Task PinAsync(IMessage msg, DiscordRestClient client) + public static async Task PinAsync(IMessage msg, DiscordClient client) { await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id); } - public static async Task UnpinAsync(IMessage msg, DiscordRestClient client) + public static async Task UnpinAsync(IMessage msg, DiscordClient client) { await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs b/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs index c22bf4504..3d9492f2e 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs @@ -2,6 +2,7 @@ namespace Discord { + //TODO: Rename to Attachment? public class RestAttachment : IAttachment { public ulong Id { get; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 58ab4c80b..33ce9c8f0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -29,12 +29,12 @@ namespace Discord.Rest public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal RestMessage(DiscordRestClient discord, ulong id, ulong channelId) + internal RestMessage(DiscordClient discord, ulong id, ulong channelId) : base(discord, id) { ChannelId = channelId; } - internal static RestMessage Create(DiscordRestClient discord, Model model) + internal static RestMessage Create(DiscordClient discord, Model model) { if (model.Type == MessageType.Default) return RestUserMessage.Create(discord, model); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index 3b805696f..837fd158f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -8,11 +8,11 @@ namespace Discord.Rest { public MessageType Type { get; private set; } - internal RestSystemMessage(DiscordRestClient discord, ulong id, ulong channelId) + internal RestSystemMessage(DiscordClient discord, ulong id, ulong channelId) : base(discord, id, channelId) { } - internal new static RestSystemMessage Create(DiscordRestClient discord, Model model) + internal new static RestSystemMessage Create(DiscordClient discord, Model model) { var entity = new RestSystemMessage(discord, model.Id, model.ChannelId); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 4db6cc372..b81fbf17f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -30,11 +30,11 @@ namespace Discord.Rest public override IReadOnlyCollection MentionedRoles => _mentionedRoles; public override IReadOnlyCollection MentionedUsers => _mentionedUsers; - internal RestUserMessage(DiscordRestClient discord, ulong id, ulong channelId) + internal RestUserMessage(DiscordClient discord, ulong id, ulong channelId) : base(discord, id, channelId) { } - internal new static RestUserMessage Create(DiscordRestClient discord, Model model) + internal new static RestUserMessage Create(DiscordClient discord, Model model) { var entity = new RestUserMessage(discord, model.Id, model.ChannelId); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index 3131cc96d..035e6c212 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -17,11 +17,11 @@ namespace Discord.Rest public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId); - internal RestApplication(DiscordRestClient discord, ulong id) + internal RestApplication(DiscordClient discord, ulong id) : base(discord, id) { } - internal static RestApplication Create(DiscordRestClient discord, Model model) + internal static RestApplication Create(DiscordClient discord, Model model) { var entity = new RestApplication(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/RestEntity.cs b/src/Discord.Net.Rest/Entities/RestEntity.cs index 496dfe9c6..d5bad8147 100644 --- a/src/Discord.Net.Rest/Entities/RestEntity.cs +++ b/src/Discord.Net.Rest/Entities/RestEntity.cs @@ -5,10 +5,10 @@ namespace Discord.Rest public abstract class RestEntity : IEntity where T : IEquatable { + public DiscordClient Discord { get; } public T Id { get; } - public DiscordRestClient Discord { get; } - public RestEntity(DiscordRestClient discord, T id) + internal RestEntity(DiscordClient discord, T id) { Discord = discord; Id = id; diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index ccac5f3ad..b0f3255f4 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -21,11 +21,11 @@ namespace Discord.Rest public bool IsEveryone => Id == Guild.Id; public string Mention => MentionUtils.MentionRole(Id); - internal RestRole(DiscordRestClient discord, ulong id) + internal RestRole(DiscordClient discord, ulong id) : base(discord, id) { } - internal static RestRole Create(DiscordRestClient discord, Model model) + internal static RestRole Create(DiscordClient discord, Model model) { var entity = new RestRole(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs index 864e81630..dc66e9288 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -7,11 +7,11 @@ namespace Discord.Rest internal static class RoleHelper { //General - public static async Task DeleteAsync(IRole role, DiscordRestClient client) + public static async Task DeleteAsync(IRole role, DiscordClient client) { await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id).ConfigureAwait(false); } - public static async Task ModifyAsync(IRole role, DiscordRestClient client, + public static async Task ModifyAsync(IRole role, DiscordClient client, Action func) { var args = new ModifyGuildRoleParams(); diff --git a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs index a8ea34550..a9ef2907d 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs @@ -6,11 +6,11 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestGroupUser : RestUser, IGroupUser { - internal RestGroupUser(DiscordRestClient discord, ulong id) + internal RestGroupUser(DiscordClient discord, ulong id) : base(discord, id) { } - internal new static RestGroupUser Create(DiscordRestClient discord, Model model) + internal new static RestGroupUser Create(DiscordClient discord, Model model) { var entity = new RestGroupUser(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index c2e25f735..489672e31 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -21,11 +21,11 @@ namespace Discord.Rest public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); - internal RestGuildUser(DiscordRestClient discord, ulong id) + internal RestGuildUser(DiscordClient discord, ulong id) : base(discord, id) { } - internal static RestGuildUser Create(DiscordRestClient discord, Model model) + internal static RestGuildUser Create(DiscordClient discord, Model model) { var entity = new RestGuildUser(discord, model.User.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs index 389a2d57d..115db9ac5 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -13,11 +13,11 @@ namespace Discord.Rest public bool IsVerified { get; private set; } public bool IsMfaEnabled { get; private set; } - internal RestSelfUser(DiscordRestClient discord, ulong id) + internal RestSelfUser(DiscordClient discord, ulong id) : base(discord, id) { } - internal new static RestSelfUser Create(DiscordRestClient discord, Model model) + internal new static RestSelfUser Create(DiscordClient discord, Model model) { var entity = new RestSelfUser(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 05e4aa3b2..492a18bd4 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -18,11 +18,11 @@ namespace Discord.Rest public virtual Game? Game => null; public virtual UserStatus Status => UserStatus.Unknown; - internal RestUser(DiscordRestClient discord, ulong id) + internal RestUser(DiscordClient discord, ulong id) : base(discord, id) { } - internal static RestUser Create(DiscordRestClient discord, Model model) + internal static RestUser Create(DiscordClient discord, Model model) { var entity = new RestUser(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index 08a59f2c0..da8e57fad 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -8,22 +8,22 @@ namespace Discord.Rest { internal static class UserHelper { - public static async Task GetAsync(IUser user, DiscordRestClient client) + public static async Task GetAsync(IUser user, DiscordClient client) { return await client.ApiClient.GetUserAsync(user.Id); } - public static async Task GetAsync(ISelfUser user, DiscordRestClient client) + public static async Task GetAsync(ISelfUser user, DiscordClient client) { var model = await client.ApiClient.GetMyUserAsync(); if (model.Id != user.Id) throw new InvalidOperationException("Unable to update this object using a different token."); return model; } - public static async Task GetAsync(IGuildUser user, DiscordRestClient client) + public static async Task GetAsync(IGuildUser user, DiscordClient client) { return await client.ApiClient.GetGuildMemberAsync(user.GuildId, user.Id); } - public static async Task ModifyAsync(ISelfUser user, DiscordRestClient client, Action func) + public static async Task ModifyAsync(ISelfUser user, DiscordClient client, Action func) { if (user.Id != client.CurrentUser.Id) throw new InvalidOperationException("Unable to modify this object using a different token."); @@ -32,19 +32,19 @@ namespace Discord.Rest func(args); await client.ApiClient.ModifySelfAsync(args); } - public static async Task ModifyAsync(IGuildUser user, DiscordRestClient client, Action func) + public static async Task ModifyAsync(IGuildUser user, DiscordClient client, Action func) { var args = new ModifyGuildMemberParams(); func(args); await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args); } - public static async Task KickAsync(IGuildUser user, DiscordRestClient client) + public static async Task KickAsync(IGuildUser user, DiscordClient client) { await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); } - public static async Task CreateDMChannelAsync(IUser user, DiscordRestClient client) + public static async Task CreateDMChannelAsync(IUser user, DiscordClient client) { var args = new CreateDMChannelParams(user.Id); return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); diff --git a/src/Discord.Net.Rest/project.json b/src/Discord.Net.Rest/project.json index 58cb60473..ac02cf639 100644 --- a/src/Discord.Net.Rest/project.json +++ b/src/Discord.Net.Rest/project.json @@ -1,12 +1,6 @@ { "version": "1.0.0-beta2-*", - "buildOptions": { - "compile": { - "include": [ "../Discord.Net.Utils/**.cs" ] - } - }, - "configurations": { "Release": { "buildOptions": { diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index b128f13b0..666d92105 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -3,6 +3,7 @@ using Discord.API.Rpc; using Discord.Net.Queue; using Discord.Net.Rest; using Discord.Net.WebSockets; +using Discord.Rpc; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -66,8 +67,8 @@ namespace Discord.API public ConnectionState ConnectionState { get; private set; } - public DiscordRpcApiClient(string clientId, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) - : base(restClientProvider, serializer, requestQueue) + public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) + : base(restClientProvider, userAgent, serializer, requestQueue) { _connectionLock = new SemaphoreSlim(1, 1); _clientId = clientId; diff --git a/src/Discord.Net.Rpc/AssemblyInfo.cs b/src/Discord.Net.Rpc/AssemblyInfo.cs new file mode 100644 index 000000000..8767c83af --- /dev/null +++ b/src/Discord.Net.Rpc/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Discord.Net.Test")] \ No newline at end of file diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index f916f42bf..8cbda4155 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -11,9 +11,9 @@ using System.Threading.Tasks; namespace Discord.Rpc { - public partial class DiscordRpcClient : DiscordRestClient + public partial class DiscordRpcClient : DiscordClient { - private readonly ILogger _rpcLogger; + private readonly Logger _rpcLogger; private readonly JsonSerializer _serializer; private TaskCompletionSource _connectTask; @@ -58,18 +58,7 @@ namespace Discord.Rpc }; } private static API.DiscordRpcApiClient CreateApiClient(DiscordRpcConfig config) - => new API.DiscordRpcApiClient(config.ClientId, config.Origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); - - internal override void Dispose(bool disposing) - { - if (!_isDisposed) - ApiClient.Dispose(); - } - - protected override Task ValidateTokenAsync(TokenType tokenType, string token) - { - return Task.CompletedTask; //Validation is done in DiscordRpcAPIClient - } + => new API.DiscordRpcApiClient(config.ClientId, DiscordRestConfig.UserAgent, config.Origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); /// public Task ConnectAsync() => ConnectAsync(false); @@ -371,20 +360,20 @@ namespace Discord.Rpc //Messages case "MESSAGE_CREATE": { - await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); + /*await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); var data = (payload.Value as JToken).ToObject(_serializer); var msg = new RpcMessage(this, data.Message); - await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false); + await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/ } break; case "MESSAGE_UPDATE": { - await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); + /*await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); var data = (payload.Value as JToken).ToObject(_serializer); var msg = new RpcMessage(this, data.Message); - await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false); + await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/ } break; case "MESSAGE_DELETE": diff --git a/src/Discord.Net.Rpc/Entities/IRemoteUserGuild.cs b/src/Discord.Net.Rpc/Entities/IRemoteUserGuild.cs deleted file mode 100644 index 5f92edb9b..000000000 --- a/src/Discord.Net.Rpc/Entities/IRemoteUserGuild.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Discord.Rpc -{ - /*public interface IRemoteUserGuild : ISnowflakeEntity - { - /// Gets the name of this guild. - string Name { get; } - }*/ -} diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs new file mode 100644 index 000000000..f98067051 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs @@ -0,0 +1,10 @@ +namespace Discord.Rpc +{ + /*internal class RpcMessage : RpcEntity, IMessage + { + internal RpcMessage(DiscordRpcClient discord, API.Message model) + : base(dicsord, model.Id) + { + } + }*/ +} diff --git a/src/Discord.Net.Rpc/Entities/RpcEntity.cs b/src/Discord.Net.Rpc/Entities/RpcEntity.cs new file mode 100644 index 000000000..9fecd77ff --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/RpcEntity.cs @@ -0,0 +1,19 @@ +using System; + +namespace Discord.Rpc +{ + public abstract class RpcEntity : IEntity + where T : IEquatable + { + public DiscordRpcClient Discord { get; } + public T Id { get; } + + internal RpcEntity(DiscordRpcClient discord, T id) + { + Discord = discord; + Id = id; + } + + IDiscordClient IEntity.Discord => Discord; + } +} diff --git a/src/Discord.Net.Rpc/Entities/RemoteUserGuild.cs b/src/Discord.Net.Rpc/Entities/RpcGuild.cs similarity index 78% rename from src/Discord.Net.Rpc/Entities/RemoteUserGuild.cs rename to src/Discord.Net.Rpc/Entities/RpcGuild.cs index 98bb1c027..c42afb975 100644 --- a/src/Discord.Net.Rpc/Entities/RemoteUserGuild.cs +++ b/src/Discord.Net.Rpc/Entities/RpcGuild.cs @@ -4,7 +4,7 @@ using Model = Discord.API.Rpc.RpcUserGuild; namespace Discord.Rpc { - /*internal class RemoteUserGuild : IRemoteUserGuild, ISnowflakeEntity + /*internal class RemoteUserGuild : RpcEntity, IRemoteUserGuild, ISnowflakeEntity { public ulong Id { get; } public DiscordRestClient Discord { get; } @@ -12,7 +12,7 @@ namespace Discord.Rpc public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); - public RemoteUserGuild(DiscordRestClient discord, Model model) + internal RemoteUserGuild(DiscordRestClient discord, Model model) { Id = model.Id; Discord = discord; diff --git a/src/Discord.Net.Rpc/Entities/RpcMessage.cs b/src/Discord.Net.Rpc/Entities/RpcMessage.cs deleted file mode 100644 index 86d67bfa4..000000000 --- a/src/Discord.Net.Rpc/Entities/RpcMessage.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Discord.Rest; - -namespace Discord.Rpc -{ - internal class RpcMessage : Message - { - public override DiscordRestClient Discord { get; } - - public RpcMessage(DiscordRpcClient discord, API.Message model) - : base(null, model.Author.IsSpecified ? new User(model.Author.Value) : null, model) - { - Discord = discord; - } - } -} diff --git a/src/Discord.Net.Rpc/project.json b/src/Discord.Net.Rpc/project.json index 9a7de9a07..0d2860743 100644 --- a/src/Discord.Net.Rpc/project.json +++ b/src/Discord.Net.Rpc/project.json @@ -1,19 +1,35 @@ { "version": "1.0.0-beta2-*", - "buildOptions": { - "compile": { - "include": [ "../Discord.Net.Utils/**.cs" ] + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } } }, "dependencies": { + "Discord.Net.Core": { + "target": "project" + }, + "Discord.Net.Rest": { + "target": "project" + }, "NETStandard.Library": "1.6.0" }, "frameworks": { - "netstandard1.6": { - "imports": "dnxcore50" + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] } } } diff --git a/src/Discord.Net.Utils/Discord.Net.Utils.projitems b/src/Discord.Net.Utils/Discord.Net.Utils.projitems deleted file mode 100644 index 2a493f22a..000000000 --- a/src/Discord.Net.Utils/Discord.Net.Utils.projitems +++ /dev/null @@ -1,26 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 2b75119c-9893-4aaa-8d38-6176eeb09060 - - - Discord - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Discord.Net.Utils/Discord.Net.Utils.shproj b/src/Discord.Net.Utils/Discord.Net.Utils.shproj deleted file mode 100644 index e3beed6a1..000000000 --- a/src/Discord.Net.Utils/Discord.Net.Utils.shproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - 2b75119c-9893-4aaa-8d38-6176eeb09060 - 14.0 - - - - - - - - diff --git a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs index 38124de63..39c929ab6 100644 --- a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs @@ -4,6 +4,7 @@ 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; @@ -31,8 +32,8 @@ namespace Discord.API public ConnectionState ConnectionState { get; private set; } - public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) - : base(restClientProvider, serializer, requestQueue) + public DiscordSocketApiClient(RestClientProvider restClientProvider, string userAgent, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) + : base(restClientProvider, userAgent, serializer, requestQueue) { _gatewayClient = webSocketProvider(); //_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) diff --git a/src/Discord.Net.WebSocket/AssemblyInfo.cs b/src/Discord.Net.WebSocket/AssemblyInfo.cs new file mode 100644 index 000000000..8767c83af --- /dev/null +++ b/src/Discord.Net.WebSocket/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Discord.Net.Test")] \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 2c4511edf..202c65da9 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -5,6 +5,7 @@ using Discord.WebSocket; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; +using System.IO; using System.Linq; using System.Net; using System.Text; @@ -34,10 +35,7 @@ namespace Discord.Audio } private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); - private readonly ILogger _audioLogger; -#if BENCHMARK - private readonly ILogger _benchmarkLogger; -#endif + private readonly Logger _audioLogger; internal readonly SemaphoreSlim _connectionLock; private readonly JsonSerializer _serializer; @@ -63,9 +61,6 @@ namespace Discord.Audio Guild = guild; _audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); -#if BENCHMARK - _benchmarkLogger = logManager.CreateLogger("Benchmark"); -#endif _connectionLock = new SemaphoreSlim(1, 1); @@ -181,11 +176,11 @@ namespace Discord.Audio ApiClient.SendAsync(data, count).ConfigureAwait(false); } - public RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000) + public Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000) { return new RTPWriteStream(this, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000); } - public OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, + public Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000) { return new OpusEncodeStream(this, _secretKey, samplesPerFrame, _ssrc, bitrate, application, bufferSize); @@ -193,11 +188,6 @@ namespace Discord.Audio private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload) { -#if BENCHMARK - Stopwatch stopwatch = Stopwatch.StartNew(); - try - { -#endif try { switch (opCode) @@ -262,15 +252,6 @@ namespace Discord.Audio await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false); return; } -#if BENCHMARK - } - finally - { - stopwatch.Stop(); - double millis = Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); - await _benchmarkLogger.DebugAsync($"{millis} ms").ConfigureAwait(false); - } -#endif } private async Task ProcessPacketAsync(byte[] packet) { diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs index c059955a8..3a650eeaf 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs @@ -1,6 +1,6 @@ namespace Discord.Audio { - public class OpusDecodeStream : RTPReadStream + internal class OpusDecodeStream : RTPReadStream { private readonly byte[] _buffer; private readonly OpusDecoder _decoder; diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs index ef773ca56..69d8b3d81 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs @@ -1,6 +1,6 @@ namespace Discord.Audio { - public class OpusEncodeStream : RTPWriteStream + internal class OpusEncodeStream : RTPWriteStream { public int SampleRate = 48000; public int Channels = 2; diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs index 4bf7f5e1b..cfc804abe 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs @@ -4,7 +4,7 @@ using System.IO; namespace Discord.Audio { - public class RTPReadStream : Stream + internal class RTPReadStream : Stream { private readonly BlockingCollection _queuedData; //TODO: Replace with max-length ring buffer private readonly AudioClient _audioClient; diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs index d547f021a..db755c877 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs @@ -3,7 +3,7 @@ using System.IO; namespace Discord.Audio { - public class RTPWriteStream : Stream + internal class RTPWriteStream : Stream { private readonly AudioClient _audioClient; private readonly byte[] _nonce, _secretKey; diff --git a/src/Discord.Net.WebSocket/DataStore.cs b/src/Discord.Net.WebSocket/DataStore.cs index 9be13b9f0..d2b93c671 100644 --- a/src/Discord.Net.WebSocket/DataStore.cs +++ b/src/Discord.Net.WebSocket/DataStore.cs @@ -12,37 +12,37 @@ namespace Discord.WebSocket private const double AverageUsersPerGuild = 47.78; //Source: Googie2149 private const double CollectionMultiplier = 1.05; //Add 5% buffer to handle growth - private readonly ConcurrentDictionary _channels; + private readonly ConcurrentDictionary _channels; private readonly ConcurrentDictionary _dmChannels; private readonly ConcurrentDictionary _guilds; private readonly ConcurrentDictionary _users; private readonly ConcurrentHashSet _groupChannels; - internal IReadOnlyCollection Channels => _channels.ToReadOnlyCollection(); + internal IReadOnlyCollection Channels => _channels.ToReadOnlyCollection(); internal IReadOnlyCollection DMChannels => _dmChannels.ToReadOnlyCollection(); 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)) + internal IReadOnlyCollection PrivateChannels => + _dmChannels.Select(x => x.Value as IPrivateChannel).Concat( + _groupChannels.Select(x => GetChannel(x) as IPrivateChannel)) .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); public DataStore(int guildCount, int dmChannelCount) { double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount; double estimatedUsersCount = guildCount * AverageUsersPerGuild; - _channels = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(estimatedChannelCount * CollectionMultiplier)); + _channels = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(estimatedChannelCount * CollectionMultiplier)); _dmChannels = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); _guilds = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); _users = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); _groupChannels = new ConcurrentHashSet(CollectionConcurrencyLevel, (int)(10 * CollectionMultiplier)); } - internal ISocketChannel GetChannel(ulong id) + internal SocketChannel GetChannel(ulong id) { - ISocketChannel channel; + SocketChannel channel; if (_channels.TryGetValue(id, out channel)) return channel; return null; @@ -54,7 +54,7 @@ namespace Discord.WebSocket return channel; return null; } - internal void AddChannel(ISocketChannel channel) + internal void AddChannel(SocketChannel channel) { _channels[channel.Id] = channel; @@ -68,9 +68,9 @@ namespace Discord.WebSocket _groupChannels.TryAdd(groupChannel.Id); } } - internal ISocketChannel RemoveChannel(ulong id) + internal SocketChannel RemoveChannel(ulong id) { - ISocketChannel channel; + SocketChannel channel; if (_channels.TryRemove(id, out channel)) { var dmChannel = channel as SocketDMChannel; diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj index da99d6e1b..45e13b5ce 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj @@ -4,18 +4,16 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - 22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d - Discord.Net.WebSocket + Discord.WebSocket .\obj .\bin\ v4.6.1 - 2.0 - + \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 00b53843d..6f93f7fa6 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1,4 +1,5 @@ -using Discord.API.Gateway; +using Discord.API; +using Discord.API.Gateway; using Discord.Audio; using Discord.Logging; using Discord.Net.Converters; @@ -11,24 +12,22 @@ 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; namespace Discord.WebSocket { - public partial class DiscordSocketClient : DiscordRestClient, IDiscordClient + public partial class DiscordSocketClient : DiscordClient, IDiscordClient { private readonly ConcurrentQueue _largeGuilds; - private readonly ILogger _gatewayLogger; -#if BENCHMARK - private readonly ILogger _benchmarkLogger; -#endif + private readonly Logger _gatewayLogger; private readonly JsonSerializer _serializer; private string _sessionId; private int _lastSeq; - private ImmutableDictionary _voiceRegions; + private ImmutableDictionary _voiceRegions; private TaskCompletionSource _connectTask; private CancellationTokenSource _cancelToken, _reconnectCancelToken; private Task _heartbeatTask, _guildDownloadTask, _reconnectTask; @@ -54,16 +53,17 @@ namespace Discord.WebSocket internal int ConnectionTimeout { get; private set; } internal WebSocketProvider WebSocketProvider { get; private set; } - public new API.DiscordSocketApiClient ApiClient => base.ApiClient as API.DiscordSocketApiClient; - internal SocketSelfUser CurrentUser => _currentUser as SocketSelfUser; + public new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; + public new SocketSelfUser CurrentUser => base.CurrentUser as SocketSelfUser; + public IReadOnlyCollection PrivateChannels => DataStore.PrivateChannels; internal IReadOnlyCollection Guilds => DataStore.Guilds; - internal IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); /// Creates a new REST/WebSocket discord client. public DiscordSocketClient() : this(new DiscordSocketConfig()) { } /// Creates a new REST/WebSocket discord client. - public DiscordSocketClient(DiscordSocketConfig config) - : base(config, CreateApiClient(config)) + public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config)) { } + private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client) + : base(config, client) { ShardId = config.ShardId; TotalShards = config.TotalShards; @@ -72,14 +72,10 @@ namespace Discord.WebSocket AudioMode = config.AudioMode; WebSocketProvider = config.WebSocketProvider; ConnectionTimeout = config.ConnectionTimeout; - DataStore = new DataStore(0, 0); + _nextAudioId = 1; - _gatewayLogger = LogManager.CreateLogger("Gateway"); -#if BENCHMARK - _benchmarkLogger = _log.CreateLogger("Benchmark"); -#endif _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; _serializer.Error += (s, e) => @@ -107,25 +103,25 @@ namespace Discord.WebSocket GuildUnavailable += async g => await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").ConfigureAwait(false); LatencyUpdated += async (old, val) => await _gatewayLogger.VerboseAsync($"Latency = {val} ms").ConfigureAwait(false); - _voiceRegions = ImmutableDictionary.Create(); + _voiceRegions = ImmutableDictionary.Create(); _largeGuilds = new ConcurrentQueue(); } private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) - => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); - + => new API.DiscordSocketApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, config.WebSocketProvider, requestQueue: new RequestQueue()); + protected override async Task OnLoginAsync(TokenType tokenType, string token) { var voiceRegions = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); - _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); + _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id); } protected override async Task OnLogoutAsync() { if (ConnectionState != ConnectionState.Disconnected) await DisconnectInternalAsync(null, false).ConfigureAwait(false); - _voiceRegions = ImmutableDictionary.Create(); + _voiceRegions = ImmutableDictionary.Create(); } - + /// public async Task ConnectAsync(bool waitForGuilds = true) { @@ -319,127 +315,55 @@ namespace Discord.WebSocket } /// - public override Task GetVoiceRegionAsync(string id) - { - VoiceRegion region; - if (_voiceRegions.TryGetValue(id, out region)) - return Task.FromResult(region); - return Task.FromResult(null); - } + public Task GetApplicationInfoAsync() + => ClientHelper.GetApplicationInfoAsync(this); + /// - public override Task GetGuildAsync(ulong id) - { - return Task.FromResult(DataStore.GetGuild(id)); - } - public override Task GetGuildEmbedAsync(ulong id) - { - var guild = DataStore.GetGuild(id); - if (guild != null) - return Task.FromResult(new GuildEmbed(guild.IsEmbeddable, guild.EmbedChannelId)); - else - return Task.FromResult(null); - } - public override Task> GetGuildSummariesAsync() - { - return Task.FromResult>(Guilds); - } - public override Task> GetGuildsAsync() + public SocketGuild GetGuild(ulong id) { - return Task.FromResult>(Guilds); + return DataStore.GetGuild(id); } - internal SocketGuild AddGuild(ExtendedGuild model, DataStore dataStore) - { - var guild = new SocketGuild(this, model, dataStore); - dataStore.AddGuild(guild); - if (model.Large) - _largeGuilds.Enqueue(model.Id); - return guild; - } - internal SocketGuild RemoveGuild(ulong id) - { - var guild = DataStore.RemoveGuild(id); - if (guild != null) - { - foreach (var channel in guild.Channels) - DataStore.RemoveChannel(id); - foreach (var user in guild.Members) - user.User.RemoveRef(this); - } - return guild; - } - /// - public override Task GetChannelAsync(ulong id) - { - return Task.FromResult(DataStore.GetChannel(id)); - } - public override Task> GetPrivateChannelsAsync() - { - return Task.FromResult>(DataStore.PrivateChannels); - } - internal ISocketChannel AddPrivateChannel(API.Channel model, DataStore dataStore) - { - switch (model.Type) - { - case ChannelType.DM: - { - var recipients = model.Recipients.Value; - var user = GetOrAddUser(recipients[0], dataStore); - var channel = new SocketDMChannel(this, new SocketDMUser(user), model); - dataStore.AddChannel(channel); - return channel; - } - case ChannelType.Group: - { - var channel = new SocketGroupChannel(this, model); - channel.UpdateUsers(model.Recipients.Value, dataStore); - dataStore.AddChannel(channel); - return channel; - } - default: - throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); - } - } - internal ISocketChannel RemovePrivateChannel(ulong id) - { - var channel = DataStore.RemoveChannel(id) as ISocketPrivateChannel; - if (channel != null) - { - foreach (var recipient in channel.Recipients) - recipient.User.RemoveRef(this); - } - return channel; - } + public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) + => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon); /// - public override Task GetUserAsync(ulong id) + public IChannel GetChannel(ulong id) { - return Task.FromResult(DataStore.GetUser(id)); + return DataStore.GetChannel(id); } + /// - public override Task GetUserAsync(string username, string discriminator) - { - return Task.FromResult(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault()); - } + public Task> GetConnectionsAsync() + => ClientHelper.GetConnectionsAsync(this); + + /// + public Task GetInviteAsync(string inviteId) + => ClientHelper.GetInviteAsync(this, inviteId); + /// - public override Task GetCurrentUserAsync() + public IUser GetUser(ulong id) { - return Task.FromResult(_currentUser); + return DataStore.GetUser(id); } - internal SocketGlobalUser GetOrAddUser(API.User model, DataStore dataStore) + /// + public IUser GetUser(string username, string discriminator) { - var user = dataStore.GetOrAddUser(model.Id, _ => new SocketGlobalUser(model)); - user.AddRef(); - return user; + return DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); } - internal SocketGlobalUser RemoveUser(ulong id) + + /// + public RestVoiceRegion GetVoiceRegion(string id) { - return DataStore.RemoveUser(id); + RestVoiceRegion region; + if (_voiceRegions.TryGetValue(id, out region)) + return region; + return null; } /// Downloads the users list for all large guilds. - public Task DownloadAllUsersAsync() + /*public Task DownloadAllUsersAsync() => DownloadUsersAsync(DataStore.Guilds.Where(x => !x.HasAllMembers)); /// Downloads the users list for the provided guilds, if they don't have a complete list. public Task DownloadUsersAsync(IEnumerable guilds) @@ -490,20 +414,10 @@ namespace Discord.WebSocket else await Task.WhenAll(batchTasks).ConfigureAwait(false); } - } + }*/ - public override Task> GetVoiceRegionsAsync() - { - return Task.FromResult>(_voiceRegions.ToReadOnlyCollection()); - } - private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) { -#if BENCHMARK - Stopwatch stopwatch = Stopwatch.StartNew(); - try - { -#endif if (seq != null) _lastSeq = seq.Value; try @@ -516,7 +430,7 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); _heartbeatTime = 0; - _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, _clientLogger); + _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, LogManager.ClientLogger); } break; case GatewayOpCode.Heartbeat: @@ -574,9 +488,9 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); var dataStore = new DataStore(data.Guilds.Length, data.PrivateChannels.Length); - var currentUser = new SocketSelfUser(this, data.User); + var currentUser = SocketSelfUser.Create(this, data.User); int unavailableGuilds = 0; - for (int i = 0; i < data.Guilds.Length; i++) + /*for (int i = 0; i < data.Guilds.Length; i++) { var model = data.Guilds[i]; var guild = AddGuild(model, dataStore); @@ -586,10 +500,10 @@ namespace Discord.WebSocket await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); } for (int i = 0; i < data.PrivateChannels.Length; i++) - AddPrivateChannel(data.PrivateChannels[i], dataStore); + AddPrivateChannel(data.PrivateChannels[i], dataStore);*/ _sessionId = data.SessionId; - _currentUser = currentUser; + base.CurrentUser = currentUser; _unavailableGuilds = unavailableGuilds; DataStore = dataStore; } @@ -603,7 +517,7 @@ namespace Discord.WebSocket await SyncGuildsAsync().ConfigureAwait(false); _lastGuildAvailableTime = Environment.TickCount; - _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, _clientLogger); + _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, LogManager.ClientLogger); await _readyEvent.InvokeAsync().ConfigureAwait(false); @@ -611,7 +525,7 @@ namespace Discord.WebSocket await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); } break; - case "RESUMED": + /*case "RESUMED": { await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); @@ -1366,7 +1280,7 @@ namespace Discord.WebSocket } else { - before = new Presence(null, UserStatus.Offline); + before = new SocketPresence(null, UserStatus.Offline); user = guild.AddOrUpdateUser(data, DataStore); } @@ -1430,7 +1344,7 @@ namespace Discord.WebSocket if (data.GuildId.HasValue) { ISocketUser user; - VoiceState before, after; + SocketVoiceState before, after; if (data.GuildId != null) { var guild = DataStore.GetGuild(data.GuildId.Value); @@ -1444,7 +1358,7 @@ namespace Discord.WebSocket if (data.ChannelId != null) { - before = guild.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false); + before = guild.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); after = guild.AddOrUpdateVoiceState(data, DataStore); if (data.UserId == _currentUser.Id) { @@ -1453,8 +1367,8 @@ namespace Discord.WebSocket } else { - before = guild.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false); - after = new VoiceState(null, data); + before = guild.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); + after = new SocketVoiceState(null, data); } user = guild.GetUser(data.UserId); @@ -1472,13 +1386,13 @@ namespace Discord.WebSocket { if (data.ChannelId != null) { - before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false); + before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); after = groupChannel.AddOrUpdateVoiceState(data, DataStore); } else { - before = groupChannel.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false); - after = new VoiceState(null, data); + before = groupChannel.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); + after = new SocketVoiceState(null, data); } user = groupChannel.GetUser(data.UserId); } @@ -1518,7 +1432,7 @@ namespace Discord.WebSocket } } - return; + return;*/ //Ignored (User only) case "CHANNEL_PINS_ACK": @@ -1550,18 +1464,9 @@ namespace Discord.WebSocket await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); return; } -#if BENCHMARK - } - finally - { - stopwatch.Stop(); - double millis = Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); - await _benchmarkLogger.DebugAsync($"{millis} ms").ConfigureAwait(false); - } -#endif } - private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken, ILogger logger) + private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken, Logger logger) { try { @@ -1601,7 +1506,7 @@ namespace Discord.WebSocket await logger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false); } } - private async Task WaitForGuildsAsync(CancellationToken cancelToken, ILogger logger) + private async Task WaitForGuildsAsync(CancellationToken cancelToken, Logger logger) { //Wait for GUILD_AVAILABLEs try @@ -1626,5 +1531,42 @@ namespace Discord.WebSocket if (guildIds.Length > 0) await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false); } + + //IDiscordClient + DiscordRestApiClient IDiscordClient.ApiClient => ApiClient; + + Task IDiscordClient.ConnectAsync() + => ConnectAsync(); + + async Task IDiscordClient.GetApplicationInfoAsync() + => await GetApplicationInfoAsync().ConfigureAwait(false); + + Task IDiscordClient.GetChannelAsync(ulong id) + => Task.FromResult(GetChannel(id)); + Task> IDiscordClient.GetPrivateChannelsAsync() + => Task.FromResult>(PrivateChannels); + + async Task> IDiscordClient.GetConnectionsAsync() + => await GetConnectionsAsync(); + + async Task IDiscordClient.GetInviteAsync(string inviteId) + => await GetInviteAsync(inviteId); + + Task IDiscordClient.GetGuildAsync(ulong id) + => Task.FromResult(GetGuild(id)); + Task> IDiscordClient.GetGuildsAsync() + => Task.FromResult>(Guilds); + async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) + => await CreateGuildAsync(name, region, jpegIcon); + + Task IDiscordClient.GetUserAsync(ulong id) + => Task.FromResult(GetUser(id)); + Task IDiscordClient.GetUserAsync(string username, string discriminator) + => Task.FromResult(GetUser(username, discriminator)); + + Task> IDiscordClient.GetVoiceRegionsAsync() + => Task.FromResult>(_voiceRegions.ToReadOnlyCollection()); + Task IDiscordClient.GetVoiceRegionAsync(string id) + => Task.FromResult(GetVoiceRegion(id)); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs new file mode 100644 index 000000000..c9f38caec --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.WebSocket +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public abstract class SocketChannel : SocketEntity, IChannel + { + internal SocketChannel(DiscordSocketClient discord, ulong id) + : base(discord, id) + { + } + internal static SocketChannel Create(DiscordSocketClient discord, Model model) + { + switch (model.Type) + { + case ChannelType.Text: + return SocketTextChannel.Create(discord, model); + case ChannelType.Voice: + return SocketVoiceChannel.Create(discord, model); + case ChannelType.DM: + return SocketDMChannel.Create(discord, model); + case ChannelType.Group: + return SocketGroupChannel.Create(discord, model); + default: + throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); + } + } + + //IChannel + IReadOnlyCollection IChannel.CachedUsers => ImmutableArray.Create(); + + IUser IChannel.GetCachedUser(ulong id) + => null; + Task IChannel.GetUserAsync(ulong id) + => Task.FromResult(null); + IAsyncEnumerable> IChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index c19508856..6b87fbae2 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -1,77 +1,138 @@ -using System.Collections.Generic; +using Discord.Rest; +using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; using System.Threading.Tasks; using MessageModel = Discord.API.Message; using Model = Discord.API.Channel; namespace Discord.WebSocket { - internal class DMChannel : IDMChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketDMChannel : SocketChannel, IDMChannel { - private readonly MessageManager _messages; + private readonly MessageCache _messages; - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public new SocketDMUser Recipient => base.Recipient as SocketDMUser; - public IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); - IReadOnlyCollection ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + public SocketUser Recipient { get; private set; } - public SocketDMChannel(DiscordSocketClient discord, SocketDMUser recipient, Model model) - : base(discord, recipient, model) + public IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); + + internal SocketDMChannel(DiscordSocketClient discord, ulong id, ulong recipientId) + : base(discord, id) { + Recipient = new SocketUser(Discord, recipientId); if (Discord.MessageCacheSize > 0) _messages = new MessageCache(Discord, this); - else - _messages = new MessageManager(Discord, this); } + internal new static SocketDMChannel Create(DiscordSocketClient discord, Model model) + { + var entity = new SocketDMChannel(discord, model.Id, model.Recipients.Value[0].Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Recipient.Update(model.Recipients.Value[0]); + } + + public Task CloseAsync() + => ChannelHelper.DeleteAsync(this, Discord); - public override Task GetUserAsync(ulong id) => Task.FromResult(GetUser(id)); - public override Task> GetUsersAsync() => Task.FromResult>(Users); - public ISocketUser GetUser(ulong id) + public SocketUser GetUser(ulong id) { - var currentUser = Discord.CurrentUser; if (id == Recipient.Id) return Recipient; - else if (id == currentUser.Id) - return currentUser; + else if (id == Discord.CurrentUser.Id) + return Discord.CurrentUser as SocketSelfUser; else return null; } - public override async Task GetMessageAsync(ulong id) - { - return await _messages.DownloadAsync(id).ConfigureAwait(false); - } - public override async Task> GetMessagesAsync(int limit) - { - return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false); - } - public override async Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - { - return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); - } - public ISocketMessage CreateMessage(ISocketUser author, MessageModel model) + public SocketMessage GetMessage(ulong id) + => _messages?.Get(id); + public async Task GetMessageAsync(ulong id, bool allowDownload = true) { - return _messages.Create(author, model); + IMessage msg = _messages?.Get(id); + if (msg == null && allowDownload) + msg = await ChannelHelper.GetMessageAsync(this, Discord, id); + return msg; } - public ISocketMessage AddMessage(ISocketUser author, MessageModel model) + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + public Task> GetPinnedMessagesAsync() + => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + + public Task SendMessageAsync(string text, bool isTTS) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); + public Task SendFileAsync(string filePath, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + + public Task DeleteMessagesAsync(IEnumerable messages) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + + public IDisposable EnterTypingState() + => ChannelHelper.EnterTypingState(this, Discord); + + internal SocketMessage AddMessage(SocketUser author, MessageModel model) { - var msg = _messages.Create(author, model); + var msg = SocketMessage.Create(Discord, author, model); _messages.Add(msg); return msg; } - public ISocketMessage GetMessage(ulong id) - { - return _messages.Get(id); - } - public ISocketMessage RemoveMessage(ulong id) + internal SocketMessage RemoveMessage(ulong id) { return _messages.Remove(id); } public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; - IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id); - ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id); - ISocketChannel ISocketChannel.Clone() => Clone(); + public override string ToString() => $"@{Recipient}"; + private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; + + //IDMChannel + IUser IDMChannel.Recipient => Recipient; + + //IPrivateChannel + IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + + //IMessageChannel + IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); + IMessage IMessageChannel.GetCachedMessage(ulong id) => null; + + async Task IMessageChannel.GetMessageAsync(ulong id) + => await GetMessageAsync(id); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) + => GetMessagesAsync(limit); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) + => GetMessagesAsync(fromMessageId, dir, limit); + async Task> IMessageChannel.GetPinnedMessagesAsync() + => await GetPinnedMessagesAsync().ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) + => await SendFileAsync(filePath, text, isTTS); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => await SendFileAsync(stream, filename, text, isTTS); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) + => await SendMessageAsync(text, isTTS); + IDisposable IMessageChannel.EnterTypingState() + => EnterTypingState(); + + //IChannel + IReadOnlyCollection IChannel.CachedUsers => Users; + + IUser IChannel.GetCachedUser(ulong id) + => GetUser(id); + Task IChannel.GetUserAsync(ulong id) + => Task.FromResult(GetUser(id)); + IAsyncEnumerable> IChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index cbe6d9c8f..7dc81fbb6 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -1,7 +1,10 @@ using Discord.Rest; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading.Tasks; using MessageModel = Discord.API.Message; @@ -11,134 +14,124 @@ using VoiceStateModel = Discord.API.VoiceState; namespace Discord.WebSocket { - internal class SocketGroupChannel : IGroupChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketGroupChannel : SocketChannel, IGroupChannel { - internal override bool IsAttached => true; + private readonly MessageCache _messages; - private readonly MessageManager _messages; - private ConcurrentDictionary _voiceStates; + private string _iconId; + private ConcurrentDictionary _users; + private ConcurrentDictionary _voiceStates; - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public IReadOnlyCollection Users - => _users.Select(x => x.Value as ISocketUser).Concat(ImmutableArray.Create(Discord.CurrentUser)).ToReadOnlyCollection(() => _users.Count + 1); - public new IReadOnlyCollection Recipients => _users.Select(x => x.Value as ISocketUser).ToReadOnlyCollection(_users); + public string Name { get; private set; } - public SocketGroupChannel(DiscordSocketClient discord, Model model) - : base(discord, model) + 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 SocketGroupChannel(DiscordSocketClient discord, ulong id) + : base(discord, id) { if (Discord.MessageCacheSize > 0) _messages = new MessageCache(Discord, this); - else - _messages = new MessageManager(Discord, this); - _voiceStates = new ConcurrentDictionary(1, 5); + _voiceStates = new ConcurrentDictionary(1, 5); + _users = new ConcurrentDictionary(1, 5); } - public override void Update(Model model) + internal new static SocketGroupChannel Create(DiscordSocketClient discord, Model model) { - if (source == UpdateSource.Rest && IsAttached) return; - - base.Update(model, source); + var entity = new SocketGroupChannel(discord, model.Id); + entity.Update(model); + return entity; } + internal void Update(Model model) + { + if (model.Name.IsSpecified) + Name = model.Name.Value; + if (model.Icon.IsSpecified) + _iconId = model.Icon.Value; - internal void UpdateUsers(UserModel[] models, DataStore dataStore) + if (model.Recipients.IsSpecified) + UpdateUsers(model.Recipients.Value); + } + internal virtual void UpdateUsers(API.User[] models) { - var users = new ConcurrentDictionary(1, models.Length); + var users = new ConcurrentDictionary(1, (int)(models.Length * 1.05)); for (int i = 0; i < models.Length; i++) - { - var globalUser = Discord.GetOrAddUser(models[i], dataStore); - users[models[i].Id] = new SocketGroupUser(this, globalUser); - } + users[models[i].Id] = SocketGroupUser.Create(Discord, models[i]); _users = users; } - internal override void UpdateUsers(UserModel[] models) - => UpdateUsers(models, source, Discord.DataStore); - public SocketGroupUser AddUser(UserModel model, DataStore dataStore) - { - GroupUser user; - if (_users.TryGetValue(model.Id, out user)) - return user as SocketGroupUser; - else - { - var globalUser = Discord.GetOrAddUser(model, dataStore); - var privateUser = new SocketGroupUser(this, globalUser); - _users[privateUser.Id] = privateUser; - return privateUser; - } - } - public ISocketUser GetUser(ulong id) + public async Task UpdateAsync() + => Update(await ChannelHelper.GetAsync(this, Discord)); + public Task LeaveAsync() + => ChannelHelper.DeleteAsync(this, Discord); + + public SocketGroupUser GetUser(ulong id) { - GroupUser user; + SocketGroupUser user; if (_users.TryGetValue(id, out user)) - return user as SocketGroupUser; - if (id == Discord.CurrentUser.Id) - return Discord.CurrentUser; - return null; - } - public SocketGroupUser RemoveUser(ulong id) - { - GroupUser user; - if (_users.TryRemove(id, out user)) - return user as SocketGroupUser; + return user; return null; } - public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary voiceStates = null) - { - var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; - var voiceState = new VoiceState(voiceChannel, model); - (voiceStates ?? _voiceStates)[model.UserId] = voiceState; - return voiceState; - } - public VoiceState? GetVoiceState(ulong id) - { - VoiceState voiceState; - if (_voiceStates.TryGetValue(id, out voiceState)) - return voiceState; - return null; - } - public VoiceState? RemoveVoiceState(ulong id) - { - VoiceState voiceState; - if (_voiceStates.TryRemove(id, out voiceState)) - return voiceState; - return null; - } + public Task GetMessageAsync(ulong id) + => ChannelHelper.GetMessageAsync(this, Discord, id); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + public Task> GetPinnedMessagesAsync() + => ChannelHelper.GetPinnedMessagesAsync(this, Discord); - public override async Task GetMessageAsync(ulong id) - { - return await _messages.DownloadAsync(id).ConfigureAwait(false); - } - public override async Task> GetMessagesAsync(int limit) - { - return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false); - } - public override async Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - { - return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); - } - public ISocketMessage CreateMessage(ISocketUser author, MessageModel model) - { - return _messages.Create(author, model); - } - public ISocketMessage AddMessage(ISocketUser author, MessageModel model) - { - var msg = _messages.Create(author, model); - _messages.Add(msg); - return msg; - } - public ISocketMessage GetMessage(ulong id) - { - return _messages.Get(id); - } - public ISocketMessage RemoveMessage(ulong id) - { - return _messages.Remove(id); - } + public Task SendMessageAsync(string text, bool isTTS) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); + public Task SendFileAsync(string filePath, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + + public Task DeleteMessagesAsync(IEnumerable messages) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + + public IDisposable EnterTypingState() + => ChannelHelper.EnterTypingState(this, Discord); + + //IPrivateChannel + IReadOnlyCollection IPrivateChannel.Recipients => Recipients; + + //IMessageChannel + IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); + + IMessage IMessageChannel.GetCachedMessage(ulong id) + => null; + async Task IMessageChannel.GetMessageAsync(ulong id) + => await GetMessageAsync(id); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) + => GetMessagesAsync(limit); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) + => GetMessagesAsync(fromMessageId, dir, limit); + async Task> IMessageChannel.GetPinnedMessagesAsync() + => await GetPinnedMessagesAsync(); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) + => await SendFileAsync(filePath, text, isTTS); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => await SendFileAsync(stream, filename, text, isTTS); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) + => await SendMessageAsync(text, isTTS); + IDisposable IMessageChannel.EnterTypingState() + => EnterTypingState(); - public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; + //IChannel + IReadOnlyCollection IChannel.CachedUsers => Users; - IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id); - ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id); - ISocketChannel ISocketChannel.Clone() => Clone(); + IUser IChannel.GetCachedUser(ulong id) + => GetUser(id); + Task IChannel.GetUserAsync(ulong id) + => Task.FromResult(GetUser(id)); + IAsyncEnumerable> IChannel.GetUsersAsync() + => 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 613a79064..8821ff616 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -1,4 +1,5 @@ using Discord.API.Rest; +using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -7,89 +8,59 @@ using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; -namespace Discord.Rest +namespace Discord.WebSocket { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal abstract class SocketGuildChannel : ISnowflakeEntity, IGuildChannel + public abstract class SocketGuildChannel : SocketChannel, IGuildChannel { - private List _overwrites; //TODO: Is maintaining a list here too expensive? Is this threadsafe? + private ImmutableArray _overwrites; - public string Name { get; private set; } - public int Position { get; private set; } + public IReadOnlyCollection PermissionOverwrites => _overwrites; - public Guild Guild { get; private set; } + public ulong GuildId { get; } - public override DiscordRestClient Discord => Guild.Discord; + public string Name { get; private set; } + public int Position { get; private set; } - public GuildChannel(Guild guild, Model model) - : base(model.Id) + internal SocketGuildChannel(DiscordSocketClient discord, ulong id, ulong guildId) + : base(discord, id) { - Guild = guild; - - Update(model); + GuildId = guildId; } - public virtual void Update(Model model) + internal new static SocketGuildChannel Create(DiscordSocketClient discord, Model model) + { + switch (model.Type) + { + case ChannelType.Text: + return SocketTextChannel.Create(discord, model); + case ChannelType.Voice: + return SocketVoiceChannel.Create(discord, model); + default: + throw new InvalidOperationException("Unknown guild channel type"); + } + } + internal virtual void Update(Model model) { - if (source == UpdateSource.Rest && IsAttached) return; - Name = model.Name.Value; Position = model.Position.Value; var overwrites = model.PermissionOverwrites.Value; - var newOverwrites = new List(overwrites.Length); + var newOverwrites = ImmutableArray.CreateBuilder(overwrites.Length); for (int i = 0; i < overwrites.Length; i++) newOverwrites.Add(new Overwrite(overwrites[i])); - _overwrites = newOverwrites; + _overwrites = newOverwrites.ToImmutable(); } public async Task UpdateAsync() - { - if (IsAttached) throw new NotSupportedException(); - - var model = await Discord.ApiClient.GetChannelAsync(Id).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyGuildChannelParams(); - func(args); - - if (!args._name.IsSpecified) - args._name = Name; - - var model = await Discord.ApiClient.ModifyGuildChannelAsync(Id, args).ConfigureAwait(false); - Update(model, UpdateSource.Rest); - } - public async Task DeleteAsync() - { - await Discord.ApiClient.DeleteChannelAsync(Id).ConfigureAwait(false); - } - - public abstract Task GetUserAsync(ulong id); - public abstract Task> GetUsersAsync(); - - public async Task> GetInvitesAsync() - { - var models = await Discord.ApiClient.GetChannelInvitesAsync(Id).ConfigureAwait(false); - return models.Select(x => new InviteMetadata(Discord, x)).ToImmutableArray(); - } - public async Task CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) - { - var args = new CreateChannelInviteParams - { - MaxAge = maxAge ?? 0, - MaxUses = maxUses ?? 0, - Temporary = isTemporary - }; - var model = await Discord.ApiClient.CreateChannelInviteAsync(Id, args).ConfigureAwait(false); - return new InviteMetadata(Discord, model); - } + => Update(await ChannelHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) + => ChannelHelper.ModifyAsync(this, Discord, func); + public Task DeleteAsync() + => ChannelHelper.DeleteAsync(this, Discord); public OverwritePermissions? GetPermissionOverwrite(IUser user) { - for (int i = 0; i < _overwrites.Count; i++) + for (int i = 0; i < _overwrites.Length; i++) { if (_overwrites[i].TargetId == user.Id) return _overwrites[i].Permissions; @@ -98,60 +69,91 @@ namespace Discord.Rest } public OverwritePermissions? GetPermissionOverwrite(IRole role) { - for (int i = 0; i < _overwrites.Count; i++) + for (int 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) { - var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue, Type = "member" }; - await Discord.ApiClient.ModifyChannelPermissionsAsync(Id, user.Id, args).ConfigureAwait(false); - _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User })); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms).ConfigureAwait(false); + _overwrites = _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User })); } public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms) { - var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue, Type = "role" }; - await Discord.ApiClient.ModifyChannelPermissionsAsync(Id, role.Id, args).ConfigureAwait(false); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms).ConfigureAwait(false); _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role })); } public async Task RemovePermissionOverwriteAsync(IUser user) { - await Discord.ApiClient.DeleteChannelPermissionAsync(Id, user.Id).ConfigureAwait(false); + await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user).ConfigureAwait(false); - for (int i = 0; i < _overwrites.Count; i++) + for (int i = 0; i < _overwrites.Length; i++) { if (_overwrites[i].TargetId == user.Id) { - _overwrites.RemoveAt(i); + _overwrites = _overwrites.RemoveAt(i); return; } } } public async Task RemovePermissionOverwriteAsync(IRole role) { - await Discord.ApiClient.DeleteChannelPermissionAsync(Id, role.Id).ConfigureAwait(false); + await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role).ConfigureAwait(false); - for (int i = 0; i < _overwrites.Count; i++) + for (int i = 0; i < _overwrites.Length; i++) { if (_overwrites[i].TargetId == role.Id) { - _overwrites.RemoveAt(i); + _overwrites = _overwrites.RemoveAt(i); return; } } } - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; - - IGuild IGuildChannel.Guild => Guild; - IReadOnlyCollection IGuildChannel.PermissionOverwrites => _overwrites.AsReadOnly(); - async Task IChannel.GetUserAsync(ulong id) => await GetUserAsync(id).ConfigureAwait(false); - async Task> IChannel.GetUsersAsync() => await GetUsersAsync().ConfigureAwait(false); + public async Task> GetInvitesAsync() + => await ChannelHelper.GetInvitesAsync(this, Discord); + public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); + + //IGuildChannel + async Task> IGuildChannel.GetInvitesAsync() + => await GetInvitesAsync(); + async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) + => await CreateInviteAsync(maxAge, maxUses, isTemporary); + + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) + => GetPermissionOverwrite(role); + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) + => GetPermissionOverwrite(user); + async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions) + => await AddPermissionOverwriteAsync(role, permissions); + async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions) + => await AddPermissionOverwriteAsync(user, permissions); + async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role) + => await RemovePermissionOverwriteAsync(role); + async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) + => await RemovePermissionOverwriteAsync(user); + + IReadOnlyCollection IGuildChannel.CachedUsers + => ImmutableArray.Create(); + IAsyncEnumerable> IGuildChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? + Task IGuildChannel.GetUserAsync(ulong id) + => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? + IGuildUser IGuildChannel.GetCachedUser(ulong id) + => null; + + //IChannel + IReadOnlyCollection IChannel.CachedUsers + => ImmutableArray.Create(); + IUser IChannel.GetCachedUser(ulong id) + => null; + IAsyncEnumerable> IChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? + Task IChannel.GetUserAsync(ulong id) + => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 72f5717c3..39a392ef2 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -1,6 +1,10 @@ -using Discord.Rest; +using Discord.API.Rest; +using Discord.Rest; +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading.Tasks; using MessageModel = Discord.API.Message; @@ -8,81 +12,105 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { - internal class SocketTextChannel : TextChannel, ISocketGuildChannel, ISocketMessageChannel + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketTextChannel : SocketGuildChannel, ITextChannel { - internal override bool IsAttached => true; + private readonly MessageCache _messages; - private readonly MessageManager _messages; + public string Topic { get; private set; } - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public new SocketGuild Guild => base.Guild as SocketGuild; + public string Mention => MentionUtils.MentionChannel(Id); - public IReadOnlyCollection Members - => Guild.Members.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray(); - - public SocketTextChannel(SocketGuild guild, Model model) - : base(guild, model) + internal SocketTextChannel(DiscordSocketClient discord, ulong id, ulong guildId) + : base(discord, id, guildId) { if (Discord.MessageCacheSize > 0) _messages = new MessageCache(Discord, this); - else - _messages = new MessageManager(Discord, this); - } - - public override Task GetUserAsync(ulong id) => Task.FromResult(GetUser(id)); - public override Task> GetUsersAsync() => Task.FromResult>(Members); - public SocketGuildUser GetUser(ulong id, bool skipCheck = false) - { - var user = Guild.GetUser(id); - if (skipCheck) return user; - - if (user != null) - { - ulong perms = Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue); - if (Permissions.GetValue(perms, ChannelPermission.ReadMessages)) - return user; - } - return null; - } - - public override async Task GetMessageAsync(ulong id) - { - return await _messages.DownloadAsync(id).ConfigureAwait(false); } - public override async Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + internal new static SocketTextChannel Create(DiscordSocketClient discord, Model model) { - return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false); + var entity = new SocketTextChannel(discord, model.Id, model.GuildId.Value); + entity.Update(model); + return entity; } - public override async Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + internal override void Update(Model model) { - return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); - } + base.Update(model); - public ISocketMessage CreateMessage(ISocketUser author, MessageModel model) - { - return _messages.Create(author, model); + Topic = model.Topic.Value; } - public ISocketMessage AddMessage(ISocketUser author, MessageModel model) + + public Task ModifyAsync(Action func) + => ChannelHelper.ModifyAsync(this, Discord, func); + + public Task GetUserAsync(ulong id) + => ChannelHelper.GetUserAsync(this, Discord, id); + public IAsyncEnumerable> GetUsersAsync() + => ChannelHelper.GetUsersAsync(this, Discord); + + public Task GetMessageAsync(ulong id) + => ChannelHelper.GetMessageAsync(this, Discord, id); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + public Task> GetPinnedMessagesAsync() + => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + + public Task SendMessageAsync(string text, bool isTTS) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); + public Task SendFileAsync(string filePath, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + + public Task DeleteMessagesAsync(IEnumerable messages) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + + public IDisposable EnterTypingState() + => ChannelHelper.EnterTypingState(this, Discord); + + internal SocketMessage AddMessage(SocketUser author, MessageModel model) { - var msg = _messages.Create(author, model); + var msg = SocketMessage.Create(Discord, author, model); _messages.Add(msg); return msg; } - public ISocketMessage GetMessage(ulong id) - { - return _messages.Get(id); - } - public ISocketMessage RemoveMessage(ulong id) + internal SocketMessage RemoveMessage(ulong id) { return _messages.Remove(id); } - public SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; + public override string ToString() => Name; + private string DebuggerDisplay => $"@{Name} ({Id}, Text)"; + + //IGuildChannel + async Task IGuildChannel.GetUserAsync(ulong id) + => await GetUserAsync(id); + IAsyncEnumerable> IGuildChannel.GetUsersAsync() + => GetUsersAsync(); - IReadOnlyCollection ISocketMessageChannel.Users => Members; + //IMessageChannel + IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); + IMessage IMessageChannel.GetCachedMessage(ulong id) => null; - IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id); - ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id, skipCheck); - ISocketChannel ISocketChannel.Clone() => Clone(); + async Task IMessageChannel.GetMessageAsync(ulong id) + => await GetMessageAsync(id); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) + => GetMessagesAsync(limit); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) + => GetMessagesAsync(fromMessageId, dir, limit); + async Task> IMessageChannel.GetPinnedMessagesAsync() + => await GetPinnedMessagesAsync().ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) + => await SendFileAsync(filePath, text, isTTS); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) + => await SendFileAsync(stream, filename, text, isTTS); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) + => await SendMessageAsync(text, isTTS); + IDisposable IMessageChannel.EnterTypingState() + => EnterTypingState(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 4f6438254..808f07e58 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -1,54 +1,50 @@ -using Discord.Audio; +using Discord.API.Rest; +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 Model = Discord.API.Channel; namespace Discord.WebSocket { - internal class SocketVoiceChannel : VoiceChannel, ISocketGuildChannel + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel { - internal override bool IsAttached => true; + public int Bitrate { get; private set; } + public int UserLimit { get; private set; } - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public new SocketGuild Guild => base.Guild as SocketGuild; - - public IReadOnlyCollection Members - => Guild.VoiceStates.Where(x => x.Value.VoiceChannel.Id == Id).Select(x => Guild.GetUser(x.Key)).ToImmutableArray(); - - public SocketVoiceChannel(SocketGuild guild, Model model) - : base(guild, model) + internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, ulong guildId) + : base(discord, id, guildId) { } - - public override Task GetUserAsync(ulong id) - => Task.FromResult(GetUser(id)); - public override Task> GetUsersAsync() - => Task.FromResult(Members); - public IGuildUser GetUser(ulong id) + internal new static SocketVoiceChannel Create(DiscordSocketClient discord, Model model) { - var user = Guild.GetUser(id); - if (user != null && user.VoiceChannel.Id == Id) - return user; - return null; + var entity = new SocketVoiceChannel(discord, model.Id, model.GuildId.Value); + entity.Update(model); + return entity; } - - public override async Task ConnectAsync() + internal override void Update(Model model) { - var audioMode = Discord.AudioMode; - if (audioMode == AudioMode.Disabled) - throw new InvalidOperationException($"Audio is not enabled on this client, {nameof(DiscordSocketConfig.AudioMode)} in {nameof(DiscordSocketConfig)} must be set."); - - return await Guild.ConnectAudioAsync(Id, - (audioMode & AudioMode.Incoming) == 0, - (audioMode & AudioMode.Outgoing) == 0).ConfigureAwait(false); + base.Update(model); + + Bitrate = model.Bitrate.Value; + UserLimit = model.UserLimit.Value; } - public SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; + public Task ModifyAsync(Action func) + => ChannelHelper.ModifyAsync(this, Discord, func); + + //IVoiceChannel + Task IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } - ISocketChannel ISocketChannel.Clone() => Clone(); + //IGuildChannel + Task IGuildChannel.GetUserAsync(ulong id) + => Task.FromResult(null); + IAsyncEnumerable> IGuildChannel.GetUsersAsync() + => ImmutableArray.Create>().ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 1870a703c..956596ace 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1,4 +1,5 @@ -using Discord.Audio; +using Discord.API.Rest; +using Discord.Audio; using Discord.Rest; using System; using System.Collections.Concurrent; @@ -19,402 +20,204 @@ using VoiceStateModel = Discord.API.VoiceState; namespace Discord.WebSocket { - internal class SocketGuild : Guild, IGuild, IUserGuild + public class SocketGuild : SocketEntity, IGuild { - internal override bool IsAttached => true; - - private readonly SemaphoreSlim _audioLock; - private TaskCompletionSource _syncPromise, _downloaderPromise; - private TaskCompletionSource _audioConnectPromise; - private ConcurrentHashSet _channels; - private ConcurrentDictionary _members; - private ConcurrentDictionary _voiceStates; + private ImmutableDictionary _roles; + private ImmutableArray _emojis; + private ImmutableArray _features; internal bool _available; - public bool Available => _available && Discord.ConnectionState == ConnectionState.Connected; - public int MemberCount { get; set; } - public int DownloadedMemberCount { get; private set; } - public AudioClient AudioClient { get; private set; } - - public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; - public bool IsSynced => _syncPromise.Task.IsCompleted; - public Task SyncPromise => _syncPromise.Task; - public Task DownloaderPromise => _downloaderPromise.Task; - - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public SocketGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id); - public IReadOnlyCollection Channels - { - get - { - var channels = _channels; - var store = Discord.DataStore; - return channels.Select(x => store.GetChannel(x) as ISocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); - } - } - public IReadOnlyCollection Members => _members.ToReadOnlyCollection(); - public IEnumerable> VoiceStates => _voiceStates; - - public SocketGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model) - { - _audioLock = new SemaphoreSlim(1, 1); - _syncPromise = new TaskCompletionSource(); - _downloaderPromise = new TaskCompletionSource(); - Update(model, dataStore); - } - - public void Update(ExtendedModel model, DataStore dataStore) - { - if (source == UpdateSource.Rest && IsAttached) return; - - _available = !(model.Unavailable ?? false); - if (!_available) - { - if (_channels == null) - _channels = new ConcurrentHashSet(); - if (_members == null) - _members = new ConcurrentDictionary(); - if (_roles == null) - _roles = new ConcurrentDictionary(); - if (Emojis == null) - Emojis = ImmutableArray.Create(); - if (Features == null) - Features = ImmutableArray.Create(); - return; - } - - base.Update(model as Model, source); - - var channels = new ConcurrentHashSet(1, (int)(model.Channels.Length * 1.05)); - { - for (int i = 0; i < model.Channels.Length; i++) - AddChannel(model.Channels[i], dataStore, channels); - } - _channels = channels; - - var members = new ConcurrentDictionary(1, (int)(model.Presences.Length * 1.05)); - { - DownloadedMemberCount = 0; - for (int i = 0; i < model.Members.Length; i++) - AddOrUpdateUser(model.Members[i], dataStore, members); - if (Discord.ApiClient.AuthTokenType != TokenType.User) - { - var _ = _syncPromise.TrySetResultAsync(true); - if (!model.Large) - _ = _downloaderPromise.TrySetResultAsync(true); - } - - for (int i = 0; i < model.Presences.Length; i++) - AddOrUpdateUser(model.Presences[i], dataStore, members); - } - _members = members; - MemberCount = model.MemberCount; - - var voiceStates = new ConcurrentDictionary(1, (int)(model.VoiceStates.Length * 1.05)); - { - for (int i = 0; i < model.VoiceStates.Length; i++) - AddOrUpdateVoiceState(model.VoiceStates[i], dataStore, voiceStates); - } - _voiceStates = voiceStates; - } - public void Update(GuildSyncModel model, DataStore dataStore) - { - if (source == UpdateSource.Rest && IsAttached) return; - - var members = new ConcurrentDictionary(1, (int)(model.Presences.Length * 1.05)); - { - DownloadedMemberCount = 0; - for (int i = 0; i < model.Members.Length; i++) - AddOrUpdateUser(model.Members[i], dataStore, members); - var _ = _syncPromise.TrySetResultAsync(true); - if (!model.Large) - _ = _downloaderPromise.TrySetResultAsync(true); - - for (int i = 0; i < model.Presences.Length; i++) - AddOrUpdateUser(model.Presences[i], dataStore, members); + 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? AFKChannelId { get; private set; } + public ulong? EmbedChannelId { 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 ulong DefaultChannelId => Id; + public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); + public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId); + public bool IsSynced => false; + + public RestRole EveryoneRole => GetRole(Id); + public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + public IReadOnlyCollection Emojis => _emojis; + public IReadOnlyCollection Features => _features; + + internal SocketGuild(DiscordSocketClient client, ulong id) + : base(client, id) + { + } + internal static SocketGuild Create(DiscordSocketClient discord, Model model) + { + var entity = new SocketGuild(discord, model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + AFKChannelId = model.AFKChannelId; + EmbedChannelId = model.EmbedChannelId; + AFKTimeout = model.AFKTimeout; + IsEmbeddable = model.EmbedEnabled; + IconId = model.Icon; + Name = model.Name; + OwnerId = model.OwnerId; + VoiceRegionId = model.Region; + SplashId = model.Splash; + VerificationLevel = model.VerificationLevel; + MfaLevel = model.MfaLevel; + DefaultMessageNotifications = model.DefaultMessageNotifications; + + if (model.Emojis != null) + { + var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); + for (int i = 0; i < model.Emojis.Length; i++) + emojis.Add(Emoji.Create(model.Emojis[i])); + _emojis = emojis.ToImmutableArray(); } - _members = members; - } - - public void Update(EmojiUpdateModel model) - { - if (source == UpdateSource.Rest && IsAttached) return; - - var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); - for (int i = 0; i < model.Emojis.Length; i++) - emojis.Add(new Emoji(model.Emojis[i])); - Emojis = emojis.ToImmutableArray(); - } - - public override Task GetChannelAsync(ulong id) => Task.FromResult(GetChannel(id)); - public override Task> GetChannelsAsync() => Task.FromResult>(Channels); - public ISocketGuildChannel AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet channels = null) - { - var channel = ToChannel(model); - (channels ?? _channels).TryAdd(model.Id); - dataStore.AddChannel(channel); - return channel; - } - public ISocketGuildChannel GetChannel(ulong id) - { - return Discord.DataStore.GetChannel(id) as ISocketGuildChannel; - } - public ISocketGuildChannel RemoveChannel(ulong id) - { - _channels.TryRemove(id); - return Discord.DataStore.RemoveChannel(id) as ISocketGuildChannel; - } - - public Role AddRole(RoleModel model, ConcurrentDictionary roles = null) - { - var role = new Role(this, model); - (roles ?? _roles)[model.Id] = role; - return role; - } - public Role RemoveRole(ulong id) - { - Role role; - if (_roles.TryRemove(id, out role)) - return role; - return null; - } - - public override Task GetUserAsync(ulong id) => Task.FromResult(GetUser(id)); - public override Task GetCurrentUserAsync() - => Task.FromResult(CurrentUser); - public override Task> GetUsersAsync() - => Task.FromResult>(Members); - public SocketGuildUser AddOrUpdateUser(MemberModel model, DataStore dataStore, ConcurrentDictionary members = null) - { - members = members ?? _members; - - SocketGuildUser member; - if (members.TryGetValue(model.User.Id, out member)) - member.Update(model, UpdateSource.WebSocket); else - { - var user = Discord.GetOrAddUser(model.User, dataStore); - member = new SocketGuildUser(this, user, model); - members[user.Id] = member; - DownloadedMemberCount++; - } - return member; - } - public SocketGuildUser AddOrUpdateUser(PresenceModel model, DataStore dataStore, ConcurrentDictionary members = null) - { - members = members ?? _members; + _emojis = ImmutableArray.Create(); - SocketGuildUser member; - if (members.TryGetValue(model.User.Id, out member)) - member.Update(model, UpdateSource.WebSocket); + if (model.Features != null) + _features = model.Features.ToImmutableArray(); else - { - var user = Discord.GetOrAddUser(model.User, dataStore); - member = new SocketGuildUser(this, user, model); - members[user.Id] = member; - DownloadedMemberCount++; - } - return member; - } - public SocketGuildUser GetUser(ulong id) - { - SocketGuildUser member; - if (_members.TryGetValue(id, out member)) - return member; - return null; - } - public SocketGuildUser RemoveUser(ulong id) - { - SocketGuildUser member; - if (_members.TryRemove(id, out member)) - { - DownloadedMemberCount--; - return member; - } - member.User.RemoveRef(Discord); + _features = ImmutableArray.Create(); + + var roles = ImmutableDictionary.CreateBuilder(); + if (model.Roles != null) + { + throw new NotImplementedException(); + } + _roles = roles.ToImmutable(); + } + + //General + public async Task UpdateAsync() + => Update(await Discord.ApiClient.GetGuildAsync(Id)); + public Task DeleteAsync() + => GuildHelper.DeleteAsync(this, Discord); + + public Task ModifyAsync(Action func) + => GuildHelper.ModifyAsync(this, Discord, func); + public Task ModifyEmbedAsync(Action func) + => GuildHelper.ModifyEmbedAsync(this, Discord, func); + public Task ModifyChannelsAsync(IEnumerable args) + => GuildHelper.ModifyChannelsAsync(this, Discord, args); + public Task ModifyRolesAsync(IEnumerable args) + => GuildHelper.ModifyRolesAsync(this, Discord, args); + + public Task LeaveAsync() + => GuildHelper.LeaveAsync(this, Discord); + + //Bans + public Task> GetBansAsync() + => GuildHelper.GetBansAsync(this, Discord); + + public Task AddBanAsync(IUser user, int pruneDays = 0) + => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays); + public Task AddBanAsync(ulong userId, int pruneDays = 0) + => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays); + + public Task RemoveBanAsync(IUser user) + => GuildHelper.RemoveBanAsync(this, Discord, user.Id); + public Task RemoveBanAsync(ulong userId) + => GuildHelper.RemoveBanAsync(this, Discord, userId); + + //Channels + public Task> GetChannelsAsync() + => GuildHelper.GetChannelsAsync(this, Discord); + public Task GetChannelAsync(ulong id) + => GuildHelper.GetChannelAsync(this, Discord, id); + public Task CreateTextChannelAsync(string name) + => GuildHelper.CreateTextChannelAsync(this, Discord, name); + public Task CreateVoiceChannelAsync(string name) + => GuildHelper.CreateVoiceChannelAsync(this, Discord, name); + + //Integrations + public Task> GetIntegrationsAsync() + => GuildHelper.GetIntegrationsAsync(this, Discord); + public Task CreateIntegrationAsync(ulong id, string type) + => GuildHelper.CreateIntegrationAsync(this, Discord, id, type); + + //Invites + public Task> GetInvitesAsync() + => GuildHelper.GetInvitesAsync(this, Discord); + + //Roles + public RestRole GetRole(ulong id) + { + RestRole value; + if (_roles.TryGetValue(id, out value)) + return value; return null; } - public override async Task DownloadUsersAsync() - { - await Discord.DownloadUsersAsync(new [] { this }); - } - public void CompleteDownloadMembers() - { - _downloaderPromise.TrySetResultAsync(true); - } - public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary voiceStates = null) + public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) { - var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; - var voiceState = new VoiceState(voiceChannel, model); - (voiceStates ?? _voiceStates)[model.UserId] = voiceState; - return voiceState; - } - public VoiceState? GetVoiceState(ulong id) - { - VoiceState voiceState; - if (_voiceStates.TryGetValue(id, out voiceState)) - return voiceState; - return null; - } - public VoiceState? RemoveVoiceState(ulong id) - { - VoiceState voiceState; - if (_voiceStates.TryRemove(id, out voiceState)) - return voiceState; - return null; - } - - public async Task ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute) - { - try - { - TaskCompletionSource promise; - - await _audioLock.WaitAsync().ConfigureAwait(false); - try - { - await DisconnectAudioInternalAsync().ConfigureAwait(false); - promise = new TaskCompletionSource(); - _audioConnectPromise = promise; - await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); - } - finally - { - _audioLock.Release(); - } - - var timeoutTask = Task.Delay(15000); - if (await Task.WhenAny(promise.Task, timeoutTask) == timeoutTask) - throw new TimeoutException(); - return await promise.Task.ConfigureAwait(false); - } - catch (Exception) - { - await DisconnectAudioInternalAsync().ConfigureAwait(false); - throw; - } - } - public async Task DisconnectAudioAsync(AudioClient client = null) - { - await _audioLock.WaitAsync().ConfigureAwait(false); - try - { - await DisconnectAudioInternalAsync(client).ConfigureAwait(false); - } - finally - { - _audioLock.Release(); - } - } - private async Task DisconnectAudioInternalAsync(AudioClient client = null) - { - var oldClient = AudioClient; - if (oldClient != null) - { - if (client == null || oldClient == client) - { - _audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection - _audioConnectPromise = null; - } - if (oldClient == client) - { - AudioClient = null; - await oldClient.DisconnectAsync().ConfigureAwait(false); - } - } - } - public async Task FinishConnectAudio(int id, string url, string token) - { - var voiceState = GetVoiceState(CurrentUser.Id).Value; - - await _audioLock.WaitAsync().ConfigureAwait(false); - try - { - if (AudioClient == null) - { - var audioClient = new AudioClient(this, id); - audioClient.Disconnected += async ex => - { - await _audioLock.WaitAsync().ConfigureAwait(false); - try - { - if (AudioClient == audioClient) //Only reconnect if we're still assigned as this guild's audio client - { - if (ex != null) - { - //Reconnect if we still have channel info. - //TODO: Is this threadsafe? Could channel data be deleted before we access it? - var voiceState2 = GetVoiceState(CurrentUser.Id); - if (voiceState2.HasValue) - { - var voiceChannelId = voiceState2.Value.VoiceChannel?.Id; - if (voiceChannelId != null) - await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted); - } - } - else - { - try { AudioClient.Dispose(); } catch { } - AudioClient = null; - } - } - } - finally - { - _audioLock.Release(); - } - }; - AudioClient = audioClient; - } - await AudioClient.ConnectAsync(url, CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); - await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - await DisconnectAudioAsync(); - } - catch (Exception e) - { - await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false); - await DisconnectAudioAsync(); - } - finally - { - _audioLock.Release(); - } - } - public async Task FinishJoinAudioChannel() - { - await _audioLock.WaitAsync().ConfigureAwait(false); - try - { - if (AudioClient != null) - await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false); - } - finally - { - _audioLock.Release(); - } - } - - public SocketGuild Clone() => MemberwiseClone() as SocketGuild; - - new internal ISocketGuildChannel ToChannel(ChannelModel model) - { - switch (model.Type) - { - case ChannelType.Text: - return new SocketTextChannel(this, model); - case ChannelType.Voice: - return new SocketVoiceChannel(this, model); - default: - throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); - } + var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); + _roles = _roles.Add(role.Id, role); + return role; } - bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id; - GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions; - IAudioClient IGuild.AudioClient => AudioClient; + //Users + public Task> GetUsersAsync() + => GuildHelper.GetUsersAsync(this, Discord); + public Task GetUserAsync(ulong id) + => GuildHelper.GetUserAsync(this, Discord, id); + public Task GetCurrentUserAsync() + => GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id); + + public Task PruneUsersAsync(int days = 30, bool simulate = false) + => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); + + //IGuild + bool IGuild.Available => true; + IAudioClient IGuild.AudioClient => null; + IReadOnlyCollection IGuild.CachedUsers => ImmutableArray.Create(); + IRole IGuild.EveryoneRole => EveryoneRole; + IReadOnlyCollection IGuild.Roles => Roles; + + async Task> IGuild.GetBansAsync() + => await GetBansAsync(); + + async Task> IGuild.GetChannelsAsync() + => await GetChannelsAsync(); + async Task IGuild.GetChannelAsync(ulong id) + => await GetChannelAsync(id); + IGuildChannel IGuild.GetCachedChannel(ulong id) + => null; + async Task IGuild.CreateTextChannelAsync(string name) + => await CreateTextChannelAsync(name); + async Task IGuild.CreateVoiceChannelAsync(string name) + => await CreateVoiceChannelAsync(name); + + async Task> IGuild.GetIntegrationsAsync() + => await GetIntegrationsAsync(); + async Task IGuild.CreateIntegrationAsync(ulong id, string type) + => await CreateIntegrationAsync(id, type); + + async Task> IGuild.GetInvitesAsync() + => await GetInvitesAsync(); + + IRole IGuild.GetRole(ulong id) + => GetRole(id); + + async Task> IGuild.GetUsersAsync() + => await GetUsersAsync(); + async Task IGuild.GetUserAsync(ulong id) + => await GetUserAsync(id); + IGuildUser IGuild.GetCachedUser(ulong id) + => null; + async Task IGuild.GetCurrentUserAsync() + => await GetCurrentUserAsync(); + Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs deleted file mode 100644 index bd65a3cc8..000000000 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildIntegration.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Discord.API.Rest; -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using Model = Discord.API.Integration; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - internal class GuildIntegration : IEntity, IGuildIntegration - { - private long _syncedAtTicks; - - public string Name { get; private set; } - public string Type { get; private set; } - public bool IsEnabled { get; private set; } - public bool IsSyncing { get; private set; } - public ulong ExpireBehavior { get; private set; } - public ulong ExpireGracePeriod { get; private set; } - - public Guild Guild { get; private set; } - public Role Role { get; private set; } - public User User { get; private set; } - public IntegrationAccount Account { get; private set; } - - public override DiscordRestClient Discord => Guild.Discord; - public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); - - public GuildIntegration(Guild guild, Model model) - : base(model.Id) - { - Guild = guild; - Update(model); - } - - public void Update(Model model) - { - Name = model.Name; - Type = model.Type; - IsEnabled = model.Enabled; - IsSyncing = model.Syncing; - ExpireBehavior = model.ExpireBehavior; - ExpireGracePeriod = model.ExpireGracePeriod; - _syncedAtTicks = model.SyncedAt.UtcTicks; - - Role = Guild.GetRole(model.RoleId); - User = new User(model.User); - } - - public async Task DeleteAsync() - { - await Discord.ApiClient.DeleteGuildIntegrationAsync(Guild.Id, Id).ConfigureAwait(false); - } - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyGuildIntegrationParams(); - func(args); - var model = await Discord.ApiClient.ModifyGuildIntegrationAsync(Guild.Id, Id, args).ConfigureAwait(false); - - Update(model, UpdateSource.Rest); - } - public async Task SyncAsync() - { - await Discord.ApiClient.SyncGuildIntegrationAsync(Guild.Id, Id).ConfigureAwait(false); - } - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; - - IGuild IGuildIntegration.Guild => Guild; - IUser IGuildIntegration.User => User; - IRole IGuildIntegration.Role => Role; - } -} diff --git a/src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs deleted file mode 100644 index c4f112b77..000000000 --- a/src/Discord.Net.WebSocket/Entities/Messages/ISocketMessage.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Model = Discord.API.Message; - -namespace Discord.WebSocket -{ - internal interface ISocketMessage : IMessage - { - DiscordSocketClient Discord { get; } - new ISocketMessageChannel Channel { get; } - - void Update(Model model); - ISocketMessage Clone(); - } -} diff --git a/src/Discord.Net.WebSocket/Entities/Utilities/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs similarity index 59% rename from src/Discord.Net.WebSocket/Entities/Utilities/MessageCache.cs rename to src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index 7fafebba5..d283f46b0 100644 --- a/src/Discord.Net.WebSocket/Entities/Utilities/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -1,4 +1,6 @@ -using System; +using Discord.Rest; +using Discord.WebSocket; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -7,53 +9,52 @@ using System.Threading.Tasks; namespace Discord.WebSocket { - internal class MessageCache : MessageManager + internal class MessageCache { - private readonly ConcurrentDictionary _messages; + private readonly ConcurrentDictionary _messages; private readonly ConcurrentQueue _orderedMessages; private readonly int _size; - public override IReadOnlyCollection Messages => _messages.ToReadOnlyCollection(); + public IReadOnlyCollection Messages => _messages.ToReadOnlyCollection(); - public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel) - : base(discord, channel) + public MessageCache(DiscordSocketClient discord, IChannel channel) { _size = discord.MessageCacheSize; - _messages = new ConcurrentDictionary(1, (int)(_size * 1.05)); + _messages = new ConcurrentDictionary(1, (int)(_size * 1.05)); _orderedMessages = new ConcurrentQueue(); } - public override void Add(ISocketMessage message) + public void Add(SocketMessage message) { if (_messages.TryAdd(message.Id, message)) { _orderedMessages.Enqueue(message.Id); ulong msgId; - ISocketMessage msg; + SocketMessage msg; while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) _messages.TryRemove(msgId, out msg); } } - public override ISocketMessage Remove(ulong id) + public SocketMessage Remove(ulong id) { - ISocketMessage msg; + SocketMessage msg; _messages.TryRemove(id, out msg); return msg; } - public override ISocketMessage Get(ulong id) + public SocketMessage Get(ulong id) { - ISocketMessage result; + SocketMessage result; if (_messages.TryGetValue(id, out result)) return result; return null; } - public override IImmutableList GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + public IImmutableList GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); - if (limit == 0) return ImmutableArray.Empty; + if (limit == 0) return ImmutableArray.Empty; IEnumerable cachedMessageIds; if (fromMessageId == null) @@ -67,7 +68,7 @@ namespace Discord.WebSocket .Take(limit) .Select(x => { - ISocketMessage msg; + SocketMessage msg; if (_messages.TryGetValue(x, out msg)) return msg; return null; @@ -75,13 +76,5 @@ namespace Discord.WebSocket .Where(x => x != null) .ToImmutableArray(); } - - public override async Task DownloadAsync(ulong id) - { - var msg = Get(id); - if (msg != null) - return msg; - return await base.DownloadAsync(id).ConfigureAwait(false); - } } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs new file mode 100644 index 000000000..46271c9e6 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Message; + +namespace Discord.WebSocket +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public abstract class SocketMessage : SocketEntity, IMessage, IUpdateable + { + private long _timestampTicks; + + public ulong ChannelId { get; } + public SocketUser Author { get; } + + public string Content { get; private set; } + + 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 MentionedRoles => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + + public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); + + internal SocketMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author) + : base(discord, id) + { + ChannelId = channelId; + Author = author; + } + internal static SocketMessage Create(DiscordSocketClient discord, SocketUser author, Model model) + { + if (model.Type == MessageType.Default) + return SocketUserMessage.Create(discord, author, model); + else + return SocketSystemMessage.Create(discord, author, model); + } + internal virtual void Update(Model model) + { + if (model.Timestamp.IsSpecified) + _timestampTicks = model.Timestamp.Value.UtcTicks; + + if (model.Content.IsSpecified) + Content = model.Content.Value; + } + + public async Task UpdateAsync() + { + var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id).ConfigureAwait(false); + Update(model); + } + + //IMessage + IUser IMessage.Author => Author; + MessageType IMessage.Type => MessageType.Default; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs index bcd95ddf2..56a8bafaa 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs @@ -3,18 +3,25 @@ using Model = Discord.API.Message; namespace Discord.WebSocket { - internal class SocketSystemMessage : SystemMessage, ISocketMessage + internal class SocketSystemMessage : SocketMessage, ISystemMessage { - internal override bool IsAttached => true; + public MessageType Type { get; private set; } - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel; - - public SocketSystemMessage(ISocketMessageChannel channel, IUser author, Model model) - : base(channel, author, model) + internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author) + : base(discord, id, channelId, author) + { + } + internal new static SocketSystemMessage Create(DiscordSocketClient discord, SocketUser author, Model model) { + var entity = new SocketSystemMessage(discord, model.Id, model.ChannelId, author); + entity.Update(model); + return entity; } + internal override void Update(Model model) + { + base.Update(model); - public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage; + Type = model.Type; + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 31ef2082a..1f60ac8f2 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -1,20 +1,133 @@ -using Discord.Rest; +using Discord.API.Rest; +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; using Model = Discord.API.Message; namespace Discord.WebSocket { - internal class SocketUserMessage : UserMessage, ISocketMessage + internal class SocketUserMessage : SocketMessage, IUserMessage { - internal override bool IsAttached => true; + private bool _isMentioningEveryone, _isTTS, _isPinned; + private long? _editedTimestampTicks; + private ImmutableArray _attachments; + private ImmutableArray _embeds; + private ImmutableArray _mentionedChannelIds; + private ImmutableArray _mentionedRoles; + private ImmutableArray _mentionedUsers; - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel; + public override bool IsTTS => _isTTS; + public override bool IsPinned => _isPinned; + public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); - public SocketUserMessage(ISocketMessageChannel channel, IUser author, Model model) - : base(channel, author, model) + public override IReadOnlyCollection Attachments => _attachments; + public override IReadOnlyCollection Embeds => _embeds; + public override IReadOnlyCollection MentionedChannelIds => _mentionedChannelIds; + public override IReadOnlyCollection MentionedRoles => _mentionedRoles; + public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + + internal SocketUserMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author) + : base(discord, id, channelId, author) + { + } + internal new static SocketUserMessage Create(DiscordSocketClient discord, SocketUser author, Model model) + { + var entity = new SocketUserMessage(discord, model.Id, model.ChannelId, author); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) { + base.Update(model); + + if (model.IsTextToSpeech.IsSpecified) + _isTTS = model.IsTextToSpeech.Value; + if (model.Pinned.IsSpecified) + _isPinned = model.Pinned.Value; + if (model.EditedTimestamp.IsSpecified) + _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; + if (model.MentionEveryone.IsSpecified) + _isMentioningEveryone = model.MentionEveryone.Value; + + if (model.Attachments.IsSpecified) + { + var value = model.Attachments.Value; + if (value.Length > 0) + { + var attachments = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + attachments.Add(RestAttachment.Create(value[i])); + _attachments = attachments.ToImmutable(); + } + else + _attachments = ImmutableArray.Create(); + } + + if (model.Embeds.IsSpecified) + { + var value = model.Embeds.Value; + if (value.Length > 0) + { + var embeds = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + embeds.Add(RestEmbed.Create(value[i])); + _embeds = embeds.ToImmutable(); + } + else + _embeds = ImmutableArray.Create(); + } + + ImmutableArray mentions = ImmutableArray.Create(); + if (model.Mentions.IsSpecified) + { + var value = model.Mentions.Value; + if (value.Length > 0) + { + var newMentions = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + newMentions.Add(RestUser.Create(Discord, value[i])); + mentions = newMentions.ToImmutable(); + } + } + + if (model.Content.IsSpecified) + { + var text = model.Content.Value; + + _mentionedUsers = MentionsHelper.GetUserMentions(text, null, mentions); + _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, null); + _mentionedRoles = MentionsHelper.GetRoleMentions(text, null); + model.Content = text; + } } - public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage; + public Task ModifyAsync(Action func) + => MessageHelper.ModifyAsync(this, Discord, func); + public Task DeleteAsync() + => MessageHelper.DeleteAsync(this, Discord); + + public Task PinAsync() + => MessageHelper.PinAsync(this, Discord); + public Task UnpinAsync() + => MessageHelper.UnpinAsync(this, Discord); + + public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, + RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) + => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); + public string Resolve(int startIndex, int length, UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, + RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) + => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); + public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, + RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) + { + text = MentionsHelper.ResolveUserMentions(text, null, MentionedUsers, userHandling); + text = MentionsHelper.ResolveChannelMentions(text, null, channelHandling); + text = MentionsHelper.ResolveRoleMentions(text, MentionedRoles, roleHandling); + text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); + return text; + } } } diff --git a/src/Discord.Net.WebSocket/Entities/SocketEntity.cs b/src/Discord.Net.WebSocket/Entities/SocketEntity.cs new file mode 100644 index 000000000..072e414f8 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/SocketEntity.cs @@ -0,0 +1,19 @@ +using System; + +namespace Discord.WebSocket +{ + public abstract class SocketEntity : IEntity + where T : IEquatable + { + public DiscordSocketClient Discord { get; } + public T Id { get; } + + internal SocketEntity(DiscordSocketClient discord, T id) + { + Discord = discord; + Id = id; + } + + IDiscordClient IEntity.Discord => Discord; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs deleted file mode 100644 index bf152eae2..000000000 --- a/src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Discord.WebSocket -{ - internal interface ISocketUser : IUser, IEntity - { - SocketGlobalUser User { get; } - - ISocketUser Clone(); - } -} diff --git a/src/Discord.Net.WebSocket/Entities/Users/Presence.cs b/src/Discord.Net.WebSocket/Entities/Users/Presence.cs deleted file mode 100644 index dad784870..000000000 --- a/src/Discord.Net.WebSocket/Entities/Users/Presence.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Discord.WebSocket -{ - //TODO: C#7 Candidate for record type - internal struct Presence : IPresence - { - public Game Game { get; } - public UserStatus Status { get; } - - public Presence(Game game, UserStatus status) - { - Game = game; - Status = status; - } - - public Presence Clone() => this; - } -} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs deleted file mode 100644 index d68a395f6..000000000 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Diagnostics; -using PresenceModel = Discord.API.Presence; - -namespace Discord.WebSocket -{ - [DebuggerDisplay("{DebuggerDisplay,nq}")] - internal class SocketDMUser : ISocketUser - { - internal bool IsAttached => true; - bool IEntity.IsAttached => IsAttached; - - public SocketGlobalUser User { get; } - - public DiscordSocketClient Discord => User.Discord; - - public Game Game => Presence.Game; - public UserStatus Status => Presence.Status; - public Presence Presence => User.Presence; //{ get; private set; } - - public ulong Id => User.Id; - public string AvatarUrl => User.AvatarUrl; - public DateTimeOffset CreatedAt => User.CreatedAt; - public string Discriminator => User.Discriminator; - public ushort DiscriminatorValue => User.DiscriminatorValue; - public bool IsBot => User.IsBot; - public string Mention => MentionUtils.Mention(this); - public string Username => User.Username; - - public SocketDMUser(SocketGlobalUser user) - { - User = user; - } - - public void Update(PresenceModel model) - { - User.Update(model, source); - } - - public SocketDMUser Clone() => MemberwiseClone() as SocketDMUser; - ISocketUser ISocketUser.Clone() => Clone(); - - public override string ToString() => $"{Username}#{Discriminator}"; - private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; - } -} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index f08a2656e..39340745c 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -1,61 +1,10 @@ -using Discord.Rest; -using System; -using Model = Discord.API.User; -using PresenceModel = Discord.API.Presence; - -namespace Discord.WebSocket +namespace Discord.WebSocket { - internal class SocketGlobalUser : User, ISocketUser + internal class SocketGlobalUser : SocketUser { - internal override bool IsAttached => true; - private readonly object _lockObj = new object(); - - private ushort _references; - - public Presence Presence { get; private set; } - - public new DiscordSocketClient Discord { get { throw new NotSupportedException(); } } - SocketGlobalUser ISocketUser.User => this; - - public SocketGlobalUser(Model model) - : base(model) + internal SocketGlobalUser(DiscordSocketClient discord, ulong id) + : base(discord, id) { } - - public void AddRef() - { - checked - { - lock (_lockObj) - _references++; - } - } - public void RemoveRef(DiscordSocketClient discord) - { - lock (_lockObj) - { - if (--_references == 0) - discord.RemoveUser(Id); - } - } - - public override void Update(Model model) - { - lock (_lockObj) - base.Update(model, source); - } - public void Update(PresenceModel model) - { - //Race conditions are okay here. Multiple shards racing already cant guarantee presence in order. - - //lock (_lockObj) - //{ - var game = model.Game != null ? new Game(model.Game) : null; - Presence = new Presence(game, model.Status); - //} - } - - public SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser; - ISocketUser ISocketUser.Clone() => Clone(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index f19dc6b9d..2c2bfe74a 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -1,36 +1,30 @@ using Discord.Rest; using System.Diagnostics; +using Model = Discord.API.User; namespace Discord.WebSocket { [DebuggerDisplay("{DebuggerDisplay,nq}")] - internal class SocketGroupUser : GroupUser, ISocketUser + public class SocketGroupUser : SocketUser, IGroupUser { - internal override bool IsAttached => true; - - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public new SocketGroupChannel Channel => base.Channel as SocketGroupChannel; - public new SocketGlobalUser User => base.User as SocketGlobalUser; - public Presence Presence => User.Presence; //{ get; private set; } - - public override Game Game => Presence.Game; - public override UserStatus Status => Presence.Status; - - public VoiceState? VoiceState => Channel.GetVoiceState(Id); - public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; - public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; - public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; - public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; - - public SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser user) - : base(channel, user) + internal SocketGroupUser(DiscordSocketClient discord, ulong id) + : base(discord, id) { } + internal new static SocketGroupUser Create(DiscordSocketClient discord, Model model) + { + var entity = new SocketGroupUser(discord, model.Id); + entity.Update(model); + return entity; + } - public SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; - ISocketUser ISocketUser.Clone() => Clone(); - - public override string ToString() => $"{Username}#{Discriminator}"; - private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; + //IVoiceState + bool IVoiceState.IsDeafened => false; + bool IVoiceState.IsMuted => false; + bool IVoiceState.IsSelfDeafened => false; + bool IVoiceState.IsSelfMuted => false; + bool IVoiceState.IsSuppressed => false; + IVoiceChannel IVoiceState.VoiceChannel => null; + string IVoiceState.VoiceSessionId => null; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 3b38eb213..33aea831c 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -1,53 +1,74 @@ -using Discord.Rest; +using Discord.API.Rest; +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; using Model = Discord.API.GuildMember; using PresenceModel = Discord.API.Presence; namespace Discord.WebSocket { - internal class SocketGuildUser : GuildUser, ISocketUser, IVoiceState + internal class SocketGuildUser : SocketUser, IGuildUser { - internal override bool IsAttached => true; - - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - public new SocketGuild Guild => base.Guild as SocketGuild; - public new SocketGlobalUser User => base.User as SocketGlobalUser; - public Presence Presence => User.Presence; //{ get; private set; } - - public override Game Game => Presence.Game; - public override UserStatus Status => Presence.Status; - - public VoiceState? VoiceState => Guild.GetVoiceState(Id); - public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; - public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; - public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; - public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; - public bool IsDeafened => VoiceState?.IsDeafened ?? false; - public bool IsMuted => VoiceState?.IsMuted ?? false; - public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; - - public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, Model model) - : base(guild, user, model) + private long? _joinedAtTicks; + private ImmutableArray _roleIds; + + public string Nickname { get; private set; } + public ulong GuildId { get; private set; } + + public IReadOnlyCollection RoleIds => _roleIds; + + public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); + + internal SocketGuildUser(DiscordSocketClient discord, ulong id) + : base(discord, id) { - //Presence = new Presence(null, UserStatus.Offline); } - public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, PresenceModel model) - : base(guild, user, model) + internal static SocketGuildUser Create(DiscordSocketClient discord, Model model) { + var entity = new SocketGuildUser(discord, model.User.Id); + entity.Update(model); + return entity; } - - public override void Update(PresenceModel model) + internal void Update(Model model) { - base.Update(model, source); + _joinedAtTicks = model.JoinedAt.UtcTicks; + if (model.Nick.IsSpecified) + Nickname = model.Nick.Value; + UpdateRoles(model.Roles); + } + private void UpdateRoles(ulong[] roleIds) + { + var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1); + roles.Add(GuildId); + for (int i = 0; i < roleIds.Length; i++) + roles.Add(roleIds[i]); + _roleIds = roles.ToImmutable(); + } - var game = model.Game != null ? new Game(model.Game) : null; - //Presence = new Presence(game, model.Status); + public override async Task UpdateAsync() + => Update(await UserHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) + => UserHelper.ModifyAsync(this, Discord, func); + public Task KickAsync() + => UserHelper.KickAsync(this, Discord); - User.Update(model, source); + public ChannelPermissions GetPermissions(IGuildChannel channel) + { + throw new NotImplementedException(); //TODO: Impl } - IVoiceChannel IVoiceState.VoiceChannel => VoiceState?.VoiceChannel; + //IGuildUser + IReadOnlyCollection IGuildUser.RoleIds => RoleIds; - public SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; - ISocketUser ISocketUser.Clone() => Clone(); + //IVoiceState + bool IVoiceState.IsDeafened => false; + bool IVoiceState.IsMuted => false; + bool IVoiceState.IsSelfDeafened => false; + bool IVoiceState.IsSelfMuted => false; + bool IVoiceState.IsSuppressed => false; + IVoiceChannel IVoiceState.VoiceChannel => null; + string IVoiceState.VoiceSessionId => null; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs new file mode 100644 index 000000000..01fd85345 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -0,0 +1,23 @@ +using Model = Discord.API.Presence; + +namespace Discord.WebSocket +{ + //TODO: C#7 Candidate for record type + internal struct SocketPresence : IPresence + { + public Game? Game { get; } + public UserStatus Status { get; } + + internal SocketPresence(Game? game, UserStatus status) + { + Game = game; + Status = status; + } + internal SocketPresence Create(Model model) + { + return new SocketPresence(model.Game != null ? Discord.Game.Create(model.Game) : (Game?)null, model.Status); + } + + public SocketPresence Clone() => this; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index e0acfbfc1..e67899de9 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -6,42 +6,39 @@ using Model = Discord.API.User; namespace Discord.WebSocket { - internal class SocketSelfUser : SelfUser, ISocketUser, ISelfUser + public class SocketSelfUser : SocketUser, ISelfUser { - internal override bool IsAttached => true; + public string Email { get; private set; } + public bool IsVerified { get; private set; } + public bool IsMfaEnabled { get; private set; } - public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; - SocketGlobalUser ISocketUser.User { get { throw new NotSupportedException(); } } - - public SocketSelfUser(DiscordSocketClient discord, Model model) - : base(discord, model) + internal SocketSelfUser(DiscordSocketClient discord, ulong id) + : base(discord, id) { } - - public async Task ModifyStatusAsync(Action func) + internal new static SocketSelfUser Create(DiscordSocketClient discord, Model model) { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new ModifyPresenceParams(); - func(args); - - var game = args._game.GetValueOrDefault(_game); - var status = args._status.GetValueOrDefault(_status); - - long idleSince = _idleSince; - if (status == UserStatus.Idle && _status != UserStatus.Idle) - idleSince = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - var apiGame = game != null ? new API.Game { Name = game.Name, StreamType = game.StreamType, StreamUrl = game.StreamUrl } : null; - - await Discord.ApiClient.SendStatusUpdateAsync(status == UserStatus.Idle ? _idleSince : (long?)null, apiGame).ConfigureAwait(false); - - //Save values - _idleSince = idleSince; - _game = game; - _status = status; + var entity = new SocketSelfUser(discord, model.Id); + entity.Update(model); + return entity; } + internal override void Update(Model model) + { + base.Update(model); + + if (model.Email.IsSpecified) + Email = model.Email.Value; + if (model.Verified.IsSpecified) + IsVerified = model.Verified.Value; + if (model.MfaEnabled.IsSpecified) + IsMfaEnabled = model.MfaEnabled.Value; + } + + public override async Task UpdateAsync() + => Update(await UserHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) + => UserHelper.ModifyAsync(this, Discord, func); - public SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; - ISocketUser ISocketUser.Clone() => Clone(); + Task ISelfUser.ModifyStatusAsync(Action func) { throw new NotSupportedException(); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs new file mode 100644 index 000000000..87691f427 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -0,0 +1,50 @@ +using Discord.Rest; +using System.Threading.Tasks; +using Model = Discord.API.User; + +namespace Discord.WebSocket +{ + public class SocketUser : SocketEntity, IUser + { + public bool IsBot { get; private set; } + public string Username { get; private set; } + public ushort DiscriminatorValue { get; private set; } + public string AvatarId { get; private set; } + + public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); + public string Discriminator => DiscriminatorValue.ToString("D4"); + public string Mention => MentionUtils.MentionUser(Id); + public virtual Game? Game => null; + public virtual UserStatus Status => UserStatus.Unknown; + + internal SocketUser(DiscordSocketClient discord, ulong id) + : base(discord, id) + { + } + internal static SocketUser Create(DiscordSocketClient discord, Model model) + { + var entity = new SocketUser(discord, model.Id); + entity.Update(model); + return entity; + } + internal virtual void Update(Model model) + { + if (model.Avatar.IsSpecified) + AvatarId = model.Avatar.Value; + if (model.Discriminator.IsSpecified) + DiscriminatorValue = ushort.Parse(model.Discriminator.Value); + if (model.Bot.IsSpecified) + IsBot = model.Bot.Value; + if (model.Username.IsSpecified) + Username = model.Username.Value; + } + + public virtual async Task UpdateAsync() + => Update(await UserHelper.GetAsync(this, Discord)); + + public Task CreateDMChannelAsync() + => UserHelper.CreateDMChannelAsync(this, Discord); + + IDMChannel IUser.GetCachedDMChannel() => null; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/VoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs similarity index 74% rename from src/Discord.Net.WebSocket/Entities/Users/VoiceState.cs rename to src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs index 123c7ee98..9813fb039 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/VoiceState.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs @@ -4,7 +4,7 @@ using Model = Discord.API.VoiceState; namespace Discord.WebSocket { //TODO: C#7 Candidate for record type - internal struct VoiceState : IVoiceState + public struct SocketVoiceState : IVoiceState { [Flags] private enum Flags : byte @@ -28,9 +28,7 @@ namespace Discord.WebSocket public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; - public VoiceState(SocketVoiceChannel voiceChannel, Model model) - : this(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress) { } - public VoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isSuppressed) + internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isSuppressed) { VoiceChannel = voiceChannel; VoiceSessionId = sessionId; @@ -44,8 +42,12 @@ 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.Suppress); + } - public VoiceState Clone() => this; + public SocketVoiceState Clone() => this; IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; } diff --git a/src/Discord.Net.WebSocket/Entities/Utilities/MessageManager.cs b/src/Discord.Net.WebSocket/Entities/Utilities/MessageManager.cs deleted file mode 100644 index 713c5b635..000000000 --- a/src/Discord.Net.WebSocket/Entities/Utilities/MessageManager.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Discord.API.Rest; -using Discord.Rest; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; -using Model = Discord.API.Message; - -namespace Discord.WebSocket -{ - internal class MessageManager - { - private readonly DiscordSocketClient _discord; - private readonly ISocketMessageChannel _channel; - - public virtual IReadOnlyCollection Messages - => ImmutableArray.Create(); - - public MessageManager(DiscordSocketClient discord, ISocketMessageChannel channel) - { - _discord = discord; - _channel = channel; - } - - public virtual void Add(ISocketMessage message) { } - public virtual ISocketMessage Remove(ulong id) => null; - public virtual ISocketMessage Get(ulong id) => null; - - public virtual IImmutableList GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ImmutableArray.Create(); - - public virtual async Task DownloadAsync(ulong id) - { - var model = await _discord.ApiClient.GetChannelMessageAsync(_channel.Id, id).ConfigureAwait(false); - if (model != null) - return Create(new User(model.Author.Value), model); - return null; - } - public async Task> DownloadAsync(ulong? fromId, Direction dir, int limit) - { - //TODO: Test heavily, especially the ordering of messages - if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); - if (limit == 0) return ImmutableArray.Empty; - - var cachedMessages = GetMany(fromId, dir, limit); - if (cachedMessages.Count == limit) - return cachedMessages; - else if (cachedMessages.Count > limit) - return cachedMessages.Skip(cachedMessages.Count - limit).ToImmutableArray(); - else - { - var args = new GetChannelMessagesParams - { - Limit = limit - cachedMessages.Count, - RelativeDirection = dir - }; - if (cachedMessages.Count == 0) - { - if (fromId != null) - args.RelativeMessageId = fromId.Value; - } - else - args.RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id; - var downloadedMessages = await _discord.ApiClient.GetChannelMessagesAsync(_channel.Id, args).ConfigureAwait(false); - - var guild = (_channel as ISocketGuildChannel)?.Guild; - return cachedMessages.Concat(downloadedMessages.Select(x => - { - IUser user = _channel.GetUser(x.Author.Value.Id, true); - if (user == null) - { - var newUser = new User(x.Author.Value); - if (guild != null) - user = new GuildUser(guild, newUser); - else - user = newUser; - } - return Create(user, x); - })).ToImmutableArray(); - } - } - - public ISocketMessage Create(IUser author, Model model) - { - if (model.Type == MessageType.Default) - return new SocketUserMessage(_channel, author, model); - else - return new SocketSystemMessage(_channel, author, model); - } - } -} diff --git a/src/Discord.Net.WebSocket/project.json b/src/Discord.Net.WebSocket/project.json index 9a7de9a07..7abff080a 100644 --- a/src/Discord.Net.WebSocket/project.json +++ b/src/Discord.Net.WebSocket/project.json @@ -2,18 +2,38 @@ "version": "1.0.0-beta2-*", "buildOptions": { - "compile": { - "include": [ "../Discord.Net.Utils/**.cs" ] + "allowUnsafe": true + }, + + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } } }, "dependencies": { + "Discord.Net.Core": { + "target": "project" + }, + "Discord.Net.Rest": { + "target": "project" + }, "NETStandard.Library": "1.6.0" }, "frameworks": { - "netstandard1.6": { - "imports": "dnxcore50" + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] } } } diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 370fde847..6bd67ae75 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,6 +1,6 @@ { "version": "1.0.0-beta2-*", - "description": "An unofficial .Net API wrapper for the Discord service.", + "description": "An aynchronous API wrapper for Discord using .NET. This package includes all of the optional Discord.Net components", "authors": [ "RogueException" ], "packOptions": { @@ -13,38 +13,22 @@ } }, - "buildOptions": { - "allowUnsafe": true, - "warningsAsErrors": false, - "xmlDoc": true - }, - - "configurations": { - "Release": { - "buildOptions": { - "define": [ "RELEASE" ], - "nowarn": [ "CS1573", "CS1591" ], - "optimize": true - } - } - }, - "dependencies": { - "Microsoft.Win32.Primitives": "4.0.1", - "Newtonsoft.Json": "8.0.3", - "System.Collections.Concurrent": "4.0.12", - "System.Collections.Immutable": "1.2.0", - "System.IO.Compression": "4.1.0", - "System.IO.FileSystem": "4.0.1", - "System.Net.Http": "4.1.0", - "System.Net.NameResolution": "4.0.0", - "System.Net.Sockets": "4.1.0", - "System.Net.WebSockets.Client": "4.0.0", - "System.Reflection.Extensions": "4.0.1", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", - "System.Runtime.Serialization.Primitives": "4.1.1", - "System.Text.RegularExpressions": "4.1.0" + "Discord.Net.Core": { + "target": "project" + }, + "Discord.Net.Rest": { + "target": "project" + }, + "Discord.Net.WebSocket": { + "target": "project" + } + //"Discord.Net.Rpc": { + // "target": "project" + //}, + //"Discord.Net.Commands": { + // "target": "project" + //} }, "frameworks": { @@ -56,4 +40,4 @@ ] } } -} +} \ No newline at end of file From e038475ab4d32b039f8341369466642f93f6f77c Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 29 Sep 2016 05:10:40 -0300 Subject: [PATCH 004/102] Removed old bucket system, cleaned up api calls. Fixed compile errors. --- .../API/DiscordRestApiClient.cs | 223 +++++++++++------- .../Net/Queue/Definitions/BucketDefinition.cs | 30 --- .../Net/Queue/Definitions/BucketGroup.cs | 9 - .../Net/Queue/Definitions/BucketTarget.cs | 9 - .../Net/Queue/Definitions/ChannelBucket.cs | 7 - .../Net/Queue/Definitions/GlobalBucket.cs | 14 -- .../Net/Queue/Definitions/GuildBucket.cs | 11 - .../Net/Queue/RequestQueue.cs | 136 ++--------- .../Net/Queue/RequestQueueBucket.cs | 51 ++-- .../Queue/{ => Requests}/IQueuedRequest.cs | 0 .../Net/Queue/Requests/JsonRestRequest.cs | 22 ++ .../Queue/Requests/MultipartRestRequest.cs | 24 ++ .../Net/Queue/Requests/RestRequest.cs | 36 +++ .../Queue/{ => Requests}/WebSocketRequest.cs | 9 +- src/Discord.Net.Core/Net/Queue/RestRequest.cs | 61 ----- .../Net/RateLimitException.cs | 4 +- .../Net/Rest/DefaultRestClient.cs | 16 +- src/Discord.Net.Core/Net/Rest/IRestClient.cs | 6 +- src/Discord.Net.Core/RequestOptions.cs | 14 ++ ...{DiscordClient.cs => BaseDiscordClient.cs} | 5 +- src/Discord.Net.Rest/ClientHelper.cs | 28 +-- src/Discord.Net.Rest/DiscordRestClient.cs | 6 +- .../Entities/Channels/ChannelHelper.cs | 44 ++-- .../Entities/Channels/RestChannel.cs | 4 +- .../Entities/Channels/RestDMChannel.cs | 4 +- .../Entities/Channels/RestGroupChannel.cs | 4 +- .../Entities/Channels/RestGuildChannel.cs | 4 +- .../Entities/Channels/RestTextChannel.cs | 4 +- .../Entities/Channels/RestVoiceChannel.cs | 4 +- .../Entities/Guilds/GuildHelper.cs | 42 ++-- .../Entities/Guilds/RestBan.cs | 2 +- .../Entities/Guilds/RestGuild.cs | 4 +- .../Entities/Guilds/RestGuildIntegration.cs | 4 +- .../Entities/Guilds/RestUserGuild.cs | 4 +- .../Entities/Guilds/RestVoiceRegion.cs | 4 +- .../Entities/Invites/InviteHelper.cs | 6 +- .../Entities/Invites/RestInvite.cs | 4 +- .../Entities/Invites/RestInviteMetadata.cs | 4 +- .../Entities/Messages/MessageHelper.cs | 10 +- .../Entities/Messages/RestMessage.cs | 4 +- .../Entities/Messages/RestSystemMessage.cs | 4 +- .../Entities/Messages/RestUserMessage.cs | 4 +- .../Entities/RestApplication.cs | 4 +- src/Discord.Net.Rest/Entities/RestEntity.cs | 4 +- .../Entities/Roles/RestRole.cs | 4 +- .../Entities/Roles/RoleHelper.cs | 4 +- .../Entities/Users/RestGroupUser.cs | 4 +- .../Entities/Users/RestGuildUser.cs | 4 +- .../Entities/Users/RestSelfUser.cs | 4 +- .../Entities/Users/RestUser.cs | 4 +- .../Entities/Users/UserHelper.cs | 14 +- .../API/DiscordRpcApiClient.cs | 39 +-- src/Discord.Net.Rpc/DiscordRpcClient.cs | 2 +- src/Discord.Net.Rpc/Entities/RpcGuild.cs | 2 - .../API/DiscordSocketApiClient.cs | 23 +- .../{DataStore.cs => ClientState.cs} | 4 +- .../DiscordSocketClient.cs | 26 +- .../Entities/Users/SocketSelfUser.cs | 1 + .../Entities/Users/SocketUser.cs | 2 +- 59 files changed, 462 insertions(+), 568 deletions(-) delete mode 100644 src/Discord.Net.Core/Net/Queue/Definitions/BucketDefinition.cs delete mode 100644 src/Discord.Net.Core/Net/Queue/Definitions/BucketGroup.cs delete mode 100644 src/Discord.Net.Core/Net/Queue/Definitions/BucketTarget.cs delete mode 100644 src/Discord.Net.Core/Net/Queue/Definitions/ChannelBucket.cs delete mode 100644 src/Discord.Net.Core/Net/Queue/Definitions/GlobalBucket.cs delete mode 100644 src/Discord.Net.Core/Net/Queue/Definitions/GuildBucket.cs rename src/Discord.Net.Core/Net/Queue/{ => Requests}/IQueuedRequest.cs (100%) create mode 100644 src/Discord.Net.Core/Net/Queue/Requests/JsonRestRequest.cs create mode 100644 src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs create mode 100644 src/Discord.Net.Core/Net/Queue/Requests/RestRequest.cs rename src/Discord.Net.Core/Net/Queue/{ => Requests}/WebSocketRequest.cs (82%) delete mode 100644 src/Discord.Net.Core/Net/Queue/RestRequest.cs rename src/Discord.Net.Rest/{DiscordClient.cs => BaseDiscordClient.cs} (96%) rename src/Discord.Net.WebSocket/{DataStore.cs => ClientState.cs} (98%) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index 8946de96b..5c0aee850 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -113,7 +113,7 @@ namespace Discord.API _authToken = token; _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); - CurrentUser = await GetMyUserAsync(); + CurrentUser = await GetMyUserAsync(new RequestOptions { IgnoreState = true }); LoginState = LoginState.LoggedIn; } @@ -155,75 +155,50 @@ namespace Discord.API internal virtual Task ConnectInternalAsync() => Task.CompletedTask; internal virtual Task DisconnectInternalAsync() => Task.CompletedTask; - //REST - public Task SendAsync(string method, string endpoint, - GlobalBucket bucket = GlobalBucket.GeneralRest, bool ignoreState = false, RequestOptions options = null) - => SendInternalAsync(method, endpoint, null, true, BucketGroup.Global, (int)bucket, 0, ignoreState, options); - public Task SendAsync(string method, string endpoint, object payload, - GlobalBucket bucket = GlobalBucket.GeneralRest, bool ignoreState = false, RequestOptions options = null) - => SendInternalAsync(method, endpoint, payload, true, BucketGroup.Global, (int)bucket, 0, ignoreState, options); - public async Task SendAsync(string method, string endpoint, - GlobalBucket bucket = GlobalBucket.GeneralRest, bool ignoreState = false, RequestOptions options = null) where TResponse : class - => DeserializeJson(await SendInternalAsync(method, endpoint, null, false, BucketGroup.Global, (int)bucket, 0, ignoreState, options).ConfigureAwait(false)); - public async Task SendAsync(string method, string endpoint, object payload, GlobalBucket bucket = - GlobalBucket.GeneralRest, bool ignoreState = false, RequestOptions options = null) where TResponse : class - => DeserializeJson(await SendInternalAsync(method, endpoint, payload, false, BucketGroup.Global, (int)bucket, 0, ignoreState, options).ConfigureAwait(false)); - - public Task SendAsync(string method, string endpoint, - GuildBucket bucket, ulong guildId, bool ignoreState = false, RequestOptions options = null) - => SendInternalAsync(method, endpoint, null, true, BucketGroup.Guild, (int)bucket, guildId, ignoreState, options); - public Task SendAsync(string method, string endpoint, object payload, - GuildBucket bucket, ulong guildId, bool ignoreState = false, RequestOptions options = null) - => SendInternalAsync(method, endpoint, payload, true, BucketGroup.Guild, (int)bucket, guildId, ignoreState, options); - public async Task SendAsync(string method, string endpoint, - GuildBucket bucket, ulong guildId, bool ignoreState = false, RequestOptions options = null) where TResponse : class - => DeserializeJson(await SendInternalAsync(method, endpoint, null, false, BucketGroup.Guild, (int)bucket, guildId, ignoreState, options).ConfigureAwait(false)); - public async Task SendAsync(string method, string endpoint, object payload, - GuildBucket bucket, ulong guildId, bool ignoreState = false, RequestOptions options = null) where TResponse : class - => DeserializeJson(await SendInternalAsync(method, endpoint, payload, false, BucketGroup.Guild, (int)bucket, guildId, ignoreState, options).ConfigureAwait(false)); - - //REST - Multipart - public Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null) - => SendMultipartInternalAsync(method, endpoint, multipartArgs, true, BucketGroup.Global, (int)bucket, 0, options); - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null) where TResponse : class - => DeserializeJson(await SendMultipartInternalAsync(method, endpoint, multipartArgs, false, BucketGroup.Global, (int)bucket, 0, options).ConfigureAwait(false)); - - public Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - GuildBucket bucket, ulong guildId, RequestOptions options = null) - => SendMultipartInternalAsync(method, endpoint, multipartArgs, true, BucketGroup.Guild, (int)bucket, guildId, options); - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - GuildBucket bucket, ulong guildId, RequestOptions options = null) where TResponse : class - => DeserializeJson(await SendMultipartInternalAsync(method, endpoint, multipartArgs, false, BucketGroup.Guild, (int)bucket, guildId, options).ConfigureAwait(false)); - //Core - private async Task SendInternalAsync(string method, string endpoint, object payload, bool headerOnly, - BucketGroup group, int bucketId, ulong guildId, bool ignoreState, RequestOptions options = null) + public async Task SendAsync(string method, string endpoint, RequestOptions options = null) { - if (!ignoreState) - CheckState(); - - var stopwatch = Stopwatch.StartNew(); - string json = null; - if (payload != null) - json = SerializeJson(payload); - var responseStream = await RequestQueue.SendAsync(new RestRequest(_restClient, method, endpoint, json, headerOnly, options), group, bucketId, guildId).ConfigureAwait(false); - stopwatch.Stop(); - - double milliseconds = ToMilliseconds(stopwatch); - await _sentRequestEvent.InvokeAsync(method, endpoint, milliseconds).ConfigureAwait(false); - - return responseStream; + options.HeaderOnly = true; + var request = new RestRequest(_restClient, method, endpoint, options); + await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); + } + public async Task SendJsonAsync(string method, string endpoint, object payload, RequestOptions options = null) + { + options.HeaderOnly = true; + var json = payload != null ? SerializeJson(payload) : null; + var request = new JsonRestRequest(_restClient, method, endpoint, json, options); + await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); } - private async Task SendMultipartInternalAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, bool headerOnly, - BucketGroup group, int bucketId, ulong guildId, RequestOptions options = null) + public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, RequestOptions options = null) { - CheckState(); + options.HeaderOnly = true; + var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); + await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); + } + public async Task SendAsync(string method, string endpoint, RequestOptions options = null) where TResponse : class + { + var request = new RestRequest(_restClient, method, endpoint, options); + return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); + } + public async Task SendJsonAsync(string method, string endpoint, object payload, RequestOptions options = null) where TResponse : class + { + 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)); + } + public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, RequestOptions options = null) + { + var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); + return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); + } + + private async Task SendInternalAsync(string method, string endpoint, RestRequest request) + { + if (!request.Options.IgnoreState) + CheckState(); var stopwatch = Stopwatch.StartNew(); - var responseStream = await RequestQueue.SendAsync(new RestRequest(_restClient, method, endpoint, multipartArgs, headerOnly, options), group, bucketId, guildId).ConfigureAwait(false); - int bytes = headerOnly ? 0 : (int)responseStream.Length; + var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false); stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); @@ -235,6 +210,7 @@ namespace Discord.API //Auth public async Task ValidateTokenAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); await SendAsync("GET", "auth/login", options: options).ConfigureAwait(false); } @@ -242,6 +218,7 @@ namespace Discord.API public async Task GetChannelAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); try { @@ -253,6 +230,7 @@ namespace Discord.API { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); try { @@ -266,6 +244,7 @@ namespace Discord.API public async Task> GetGuildChannelsAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"guilds/{guildId}/channels", options: options).ConfigureAwait(false); } @@ -275,12 +254,14 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("POST", $"guilds/{guildId}/channels", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", $"guilds/{guildId}/channels", args, options: options).ConfigureAwait(false); } public async Task DeleteChannelAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync("DELETE", $"channels/{channelId}", options: options).ConfigureAwait(false); } @@ -290,8 +271,9 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); } public async Task ModifyGuildChannelAsync(ulong channelId, ModifyTextChannelParams args, RequestOptions options = null) { @@ -299,8 +281,9 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); } public async Task ModifyGuildChannelAsync(ulong channelId, ModifyVoiceChannelParams args, RequestOptions options = null) { @@ -310,13 +293,15 @@ namespace Discord.API Preconditions.AtLeast(args.UserLimit, 0, nameof(args.Bitrate)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); } public async Task ModifyGuildChannelsAsync(ulong guildId, IEnumerable args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); var channels = args.ToArray(); switch (channels.Length) @@ -327,7 +312,7 @@ namespace Discord.API await ModifyGuildChannelAsync(channels[0].Id, new ModifyGuildChannelParams { Position = channels[0].Position }).ConfigureAwait(false); break; default: - await SendAsync("PATCH", $"guilds/{guildId}/channels", channels, options: options).ConfigureAwait(false); + await SendJsonAsync("PATCH", $"guilds/{guildId}/channels", channels, options: options).ConfigureAwait(false); break; } } @@ -337,6 +322,7 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); try { @@ -350,6 +336,7 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); 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; @@ -383,13 +370,15 @@ 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)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("POST", $"channels/{channelId}/messages", args, GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", $"channels/{channelId}/messages", args, options: options).ConfigureAwait(false); } public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); if (args.Content.GetValueOrDefault(null) == null) args.Content = ""; @@ -401,12 +390,13 @@ namespace Discord.API throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); } - return await SendMultipartAsync("POST", $"channels/{channelId}/messages", args.ToDictionary(), GlobalBucket.DirectMessage, options: options).ConfigureAwait(false); + return await SendMultipartAsync("POST", $"channels/{channelId}/messages", args.ToDictionary(), options: options).ConfigureAwait(false); } public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("DELETE", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); } @@ -414,9 +404,9 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.NotNull(args.MessageIds, nameof(args.MessageIds)); Preconditions.AtMost(args.MessageIds.Length, 100, nameof(args.MessageIds.Length)); + options = RequestOptions.CreateOrClone(options); switch (args.MessageIds.Length) { @@ -426,7 +416,7 @@ namespace Discord.API await DeleteMessageAsync(channelId, args.MessageIds[0]).ConfigureAwait(false); break; default: - await SendAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, options: options).ConfigureAwait(false); + await SendJsonAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, options: options).ConfigureAwait(false); break; } } @@ -441,19 +431,22 @@ namespace Discord.API 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)); } + options = RequestOptions.CreateOrClone(options); - return await SendAsync("PATCH", $"channels/{channelId}/messages/{messageId}", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", $"channels/{channelId}/messages/{messageId}", args, options: options).ConfigureAwait(false); } public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("POST", $"channels/{channelId}/messages/{messageId}/ack", options: options).ConfigureAwait(false); } public async Task TriggerTypingIndicatorAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("POST", $"channels/{channelId}/typing", options: options).ConfigureAwait(false); } @@ -464,13 +457,15 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(targetId, 0, nameof(targetId)); Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); - await SendAsync("PUT", $"channels/{channelId}/permissions/{targetId}", args, options: options).ConfigureAwait(false); + await SendJsonAsync("PUT", $"channels/{channelId}/permissions/{targetId}", args, options: options).ConfigureAwait(false); } public async Task DeleteChannelPermissionAsync(ulong channelId, ulong targetId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(targetId, 0, nameof(targetId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("DELETE", $"channels/{channelId}/permissions/{targetId}", options: options).ConfigureAwait(false); } @@ -480,6 +475,7 @@ namespace Discord.API { Preconditions.GreaterThan(channelId, 0, nameof(channelId)); Preconditions.GreaterThan(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("PUT", $"channels/{channelId}/pins/{messageId}", options: options).ConfigureAwait(false); @@ -488,12 +484,14 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("DELETE", $"channels/{channelId}/pins/{messageId}", options: options).ConfigureAwait(false); } public async Task> GetPinsAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"channels/{channelId}/pins", options: options).ConfigureAwait(false); } @@ -503,6 +501,7 @@ namespace Discord.API { Preconditions.GreaterThan(channelId, 0, nameof(channelId)); Preconditions.GreaterThan(userId, 0, nameof(userId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("PUT", $"channels/{channelId}/recipients/{userId}", options: options).ConfigureAwait(false); @@ -511,6 +510,7 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(userId, 0, nameof(userId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("DELETE", $"channels/{channelId}/recipients/{userId}", options: options).ConfigureAwait(false); } @@ -519,6 +519,7 @@ namespace Discord.API public async Task GetGuildAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); try { @@ -531,18 +532,21 @@ namespace Discord.API 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 SendAsync("POST", "guilds", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", "guilds", args, options: options).ConfigureAwait(false); } public async Task DeleteGuildAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync("DELETE", $"guilds/{guildId}", options: options).ConfigureAwait(false); } public async Task LeaveGuildAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync("DELETE", $"users/@me/guilds/{guildId}", options: options).ConfigureAwait(false); } @@ -555,30 +559,34 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); Preconditions.GreaterThan(args.OwnerId, 0, nameof(args.OwnerId)); Preconditions.NotNull(args.RegionId, nameof(args.RegionId)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("PATCH", $"guilds/{guildId}", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", $"guilds/{guildId}", args, options: options).ConfigureAwait(false); } 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, 0, nameof(args.Days)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("POST", $"guilds/{guildId}/prune", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", $"guilds/{guildId}/prune", args, options: options).ConfigureAwait(false); } 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, 0, nameof(args.Days)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", $"guilds/{guildId}/prune", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("GET", $"guilds/{guildId}/prune", args, options: options).ConfigureAwait(false); } //Guild Bans public async Task> GetGuildBansAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"guilds/{guildId}/bans", options: options).ConfigureAwait(false); } @@ -588,13 +596,15 @@ namespace Discord.API Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.DeleteMessageDays, 0, nameof(args.DeleteMessageDays)); + options = RequestOptions.CreateOrClone(options); - await SendAsync("PUT", $"guilds/{guildId}/bans/{userId}", args, options: options).ConfigureAwait(false); + await SendJsonAsync("PUT", $"guilds/{guildId}/bans/{userId}", args, 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); await SendAsync("DELETE", $"guilds/{guildId}/bans/{userId}", options: options).ConfigureAwait(false); } @@ -603,6 +613,7 @@ namespace Discord.API public async Task GetGuildEmbedAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); try { @@ -614,14 +625,16 @@ namespace Discord.API { Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("PATCH", $"guilds/{guildId}/embed", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", $"guilds/{guildId}/embed", args, options: options).ConfigureAwait(false); } //Guild Integrations public async Task> GetGuildIntegrationsAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"guilds/{guildId}/integrations", options: options).ConfigureAwait(false); } @@ -630,6 +643,7 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(args.Id, 0, nameof(args.Id)); + options = RequestOptions.CreateOrClone(options); return await SendAsync("POST", $"guilds/{guildId}/integrations", options: options).ConfigureAwait(false); } @@ -637,6 +651,7 @@ namespace Discord.API { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync("DELETE", $"guilds/{guildId}/integrations/{integrationId}", options: options).ConfigureAwait(false); } @@ -647,13 +662,15 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.ExpireBehavior, 0, nameof(args.ExpireBehavior)); Preconditions.AtLeast(args.ExpireGracePeriod, 0, nameof(args.ExpireGracePeriod)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args, options: options).ConfigureAwait(false); } 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); return await SendAsync("POST", $"guilds/{guildId}/integrations/{integrationId}/sync", options: options).ConfigureAwait(false); } @@ -662,6 +679,7 @@ namespace Discord.API public async Task GetInviteAsync(string inviteId, RequestOptions options = null) { Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); + options = RequestOptions.CreateOrClone(options); //Remove trailing slash if (inviteId[inviteId.Length - 1] == '/') @@ -680,12 +698,14 @@ namespace Discord.API public async Task> GetGuildInvitesAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"guilds/{guildId}/invites", options: options).ConfigureAwait(false); } public async Task> GetChannelInvitesAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"channels/{channelId}/invites", options: options).ConfigureAwait(false); } @@ -695,18 +715,21 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.MaxAge, 0, nameof(args.MaxAge)); Preconditions.AtLeast(args.MaxUses, 0, nameof(args.MaxUses)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("POST", $"channels/{channelId}/invites", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", $"channels/{channelId}/invites", args, options: options).ConfigureAwait(false); } public async Task DeleteInviteAsync(string inviteCode, RequestOptions options = null) { Preconditions.NotNullOrEmpty(inviteCode, nameof(inviteCode)); + options = RequestOptions.CreateOrClone(options); return await SendAsync("DELETE", $"invites/{inviteCode}", options: options).ConfigureAwait(false); } public async Task AcceptInviteAsync(string inviteCode, RequestOptions options = null) { Preconditions.NotNullOrEmpty(inviteCode, nameof(inviteCode)); + options = RequestOptions.CreateOrClone(options); await SendAsync("POST", $"invites/{inviteCode}", options: options).ConfigureAwait(false); } @@ -716,6 +739,7 @@ namespace Discord.API { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); + options = RequestOptions.CreateOrClone(options); try { @@ -730,6 +754,7 @@ namespace Discord.API Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); Preconditions.AtMost(args.Limit, DiscordConfig.MaxUsersPerBatch, nameof(args.Limit)); 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); @@ -741,6 +766,7 @@ namespace Discord.API { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("DELETE", $"guilds/{guildId}/members/{userId}", options: options).ConfigureAwait(false); } @@ -749,6 +775,7 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); bool isCurrentUser = userId == CurrentUser.Id; @@ -760,7 +787,7 @@ namespace Discord.API } if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified) { - await SendAsync("PATCH", $"guilds/{guildId}/members/{userId}", args, GuildBucket.ModifyMember, guildId, options: options).ConfigureAwait(false); + await SendJsonAsync("PATCH", $"guilds/{guildId}/members/{userId}", args, options: options).ConfigureAwait(false); } } @@ -768,12 +795,14 @@ namespace Discord.API public async Task> GetGuildRolesAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"guilds/{guildId}/roles", options: options).ConfigureAwait(false); } public async Task CreateGuildRoleAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync("POST", $"guilds/{guildId}/roles", options: options).ConfigureAwait(false); } @@ -781,6 +810,7 @@ namespace Discord.API { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(roleId, 0, nameof(roleId)); + options = RequestOptions.CreateOrClone(options); await SendAsync("DELETE", $"guilds/{guildId}/roles/{roleId}", options: options).ConfigureAwait(false); } @@ -792,13 +822,15 @@ namespace Discord.API Preconditions.AtLeast(args.Color, 0, nameof(args.Color)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); + options = RequestOptions.CreateOrClone(options); - return await SendAsync("PATCH", $"guilds/{guildId}/roles/{roleId}", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", $"guilds/{guildId}/roles/{roleId}", args, options: options).ConfigureAwait(false); } 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 roles = args.ToArray(); switch (roles.Length) @@ -808,7 +840,7 @@ namespace Discord.API case 1: return ImmutableArray.Create(await ModifyGuildRoleAsync(guildId, roles[0].Id, roles[0]).ConfigureAwait(false)); default: - return await SendAsync>("PATCH", $"guilds/{guildId}/roles", args, options: options).ConfigureAwait(false); + return await SendJsonAsync>("PATCH", $"guilds/{guildId}/roles", args, options: options).ConfigureAwait(false); } } @@ -816,6 +848,7 @@ namespace Discord.API public async Task GetUserAsync(ulong userId, RequestOptions options = null) { Preconditions.NotEqual(userId, 0, nameof(userId)); + options = RequestOptions.CreateOrClone(options); try { @@ -827,6 +860,7 @@ namespace Discord.API { Preconditions.NotNullOrEmpty(username, nameof(username)); Preconditions.NotNullOrEmpty(discriminator, nameof(discriminator)); + options = RequestOptions.CreateOrClone(options); try { @@ -839,6 +873,7 @@ namespace Discord.API { Preconditions.NotNullOrEmpty(query, nameof(query)); Preconditions.AtLeast(limit, 0, nameof(limit)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"users?q={Uri.EscapeDataString(query)}&limit={limit}", options: options).ConfigureAwait(false); } @@ -846,54 +881,64 @@ namespace Discord.API //Current User/DMs public async Task GetMyUserAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); return await SendAsync("GET", "users/@me", options: options).ConfigureAwait(false); } public async Task> GetMyConnectionsAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", "users/@me/connections", options: options).ConfigureAwait(false); } public async Task> GetMyPrivateChannelsAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", "users/@me/channels", options: options).ConfigureAwait(false); } public async Task> GetMyGuildsAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", "users/@me/guilds", options: options).ConfigureAwait(false); } public async Task GetMyApplicationAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); return await SendAsync("GET", "oauth2/applications/@me", options: options).ConfigureAwait(false); } 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 SendAsync("PATCH", "users/@me", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", "users/@me", args, options: options).ConfigureAwait(false); } 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); - await SendAsync("PATCH", $"guilds/{guildId}/members/@me/nick", args, options: options).ConfigureAwait(false); + await SendJsonAsync("PATCH", $"guilds/{guildId}/members/@me/nick", args, 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 SendAsync("POST", $"users/@me/channels", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", $"users/@me/channels", args, options: options).ConfigureAwait(false); } //Voice Regions public async Task> GetVoiceRegionsAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", "voice/regions", options: options).ConfigureAwait(false); } public async Task> GetGuildVoiceRegionsAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); return await SendAsync>("GET", $"guilds/{guildId}/regions", options: options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Core/Net/Queue/Definitions/BucketDefinition.cs b/src/Discord.Net.Core/Net/Queue/Definitions/BucketDefinition.cs deleted file mode 100644 index cfc53b0c8..000000000 --- a/src/Discord.Net.Core/Net/Queue/Definitions/BucketDefinition.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Discord.Net.Queue -{ - public sealed class Bucket - { - /// Gets the unique identifier for this bucket. - public string Id { get; } - /// Gets the name of this bucket. - public string Name { get; } - /// Gets the amount of requests that may be sent per window. - public int WindowCount { get; } - /// Gets the length of this bucket's window, in seconds. - public int WindowSeconds { get; } - /// Gets the type of account this bucket affects. - public BucketTarget Target { get; } - /// Gets this bucket's parent. - public GlobalBucket? Parent { get; } - - internal Bucket(string id, int windowCount, int windowSeconds, BucketTarget target, GlobalBucket? parent = null) - : this(id, id, windowCount, windowSeconds, target, parent) { } - internal Bucket(string id, string name, int windowCount, int windowSeconds, BucketTarget target, GlobalBucket? parent = null) - { - Id = id; - Name = name; - WindowCount = windowCount; - WindowSeconds = windowSeconds; - Target = target; - Parent = parent; - } - } -} diff --git a/src/Discord.Net.Core/Net/Queue/Definitions/BucketGroup.cs b/src/Discord.Net.Core/Net/Queue/Definitions/BucketGroup.cs deleted file mode 100644 index e7b0a4181..000000000 --- a/src/Discord.Net.Core/Net/Queue/Definitions/BucketGroup.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Discord.Net.Queue -{ - public enum BucketGroup - { - Global, - Guild, - Channel - } -} diff --git a/src/Discord.Net.Core/Net/Queue/Definitions/BucketTarget.cs b/src/Discord.Net.Core/Net/Queue/Definitions/BucketTarget.cs deleted file mode 100644 index 0e5a5d552..000000000 --- a/src/Discord.Net.Core/Net/Queue/Definitions/BucketTarget.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Discord.Net.Queue -{ - public enum BucketTarget - { - Client, - Bot, - Both - } -} diff --git a/src/Discord.Net.Core/Net/Queue/Definitions/ChannelBucket.cs b/src/Discord.Net.Core/Net/Queue/Definitions/ChannelBucket.cs deleted file mode 100644 index 235e6dfdf..000000000 --- a/src/Discord.Net.Core/Net/Queue/Definitions/ChannelBucket.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Discord.Net.Queue -{ - public enum ChannelBucket - { - SendEditMessage, - } -} diff --git a/src/Discord.Net.Core/Net/Queue/Definitions/GlobalBucket.cs b/src/Discord.Net.Core/Net/Queue/Definitions/GlobalBucket.cs deleted file mode 100644 index fe95ecb79..000000000 --- a/src/Discord.Net.Core/Net/Queue/Definitions/GlobalBucket.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Discord.Net.Queue -{ - public enum GlobalBucket - { - GeneralRest, - DirectMessage, - SendEditMessage, - - GeneralGateway, - UpdateStatus, - - GeneralRpc - } -} diff --git a/src/Discord.Net.Core/Net/Queue/Definitions/GuildBucket.cs b/src/Discord.Net.Core/Net/Queue/Definitions/GuildBucket.cs deleted file mode 100644 index 4089fd1e7..000000000 --- a/src/Discord.Net.Core/Net/Queue/Definitions/GuildBucket.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Discord.Net.Queue -{ - public enum GuildBucket - { - SendEditMessage, - DeleteMessage, - DeleteMessages, - ModifyMember, - Nickname - } -} diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueue.cs b/src/Discord.Net.Core/Net/Queue/RequestQueue.cs index 5300905c8..d25c1f340 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueue.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -10,91 +8,23 @@ namespace Discord.Net.Queue { public class RequestQueue { - public event Func RateLimitTriggered; - - private readonly static ImmutableDictionary _globalLimits; - private readonly static ImmutableDictionary _guildLimits; - private readonly static ImmutableDictionary _channelLimits; + public event Func RateLimitTriggered; + private readonly SemaphoreSlim _lock; - private readonly RequestQueueBucket[] _globalBuckets; - private readonly ConcurrentDictionary[] _guildBuckets; - private readonly ConcurrentDictionary[] _channelBuckets; + private readonly ConcurrentDictionary _buckets; private CancellationTokenSource _clearToken; private CancellationToken _parentToken; private CancellationToken _cancelToken; - - static RequestQueue() - { - _globalLimits = new Dictionary - { - //REST - [GlobalBucket.GeneralRest] = new Bucket(null, "rest", 0, 0, BucketTarget.Both), //No Limit - //[GlobalBucket.Login] = new BucketDefinition(1, 1), - [GlobalBucket.DirectMessage] = new Bucket("bot:msg:dm", 5, 5, BucketTarget.Bot), - [GlobalBucket.SendEditMessage] = new Bucket("bot:msg:global", 50, 10, BucketTarget.Bot), - //[GlobalBucket.Username] = new Bucket("bot:msg:global", 2, 3600, BucketTarget.Both), - - //Gateway - [GlobalBucket.GeneralGateway] = new Bucket(null, "gateway", 120, 60, BucketTarget.Both), - [GlobalBucket.UpdateStatus] = new Bucket(null, "status", 5, 1, BucketTarget.Both, GlobalBucket.GeneralGateway), - - //Rpc - [GlobalBucket.GeneralRpc] = new Bucket(null, "rpc", 120, 60, BucketTarget.Both) - }.ToImmutableDictionary(); - - _guildLimits = new Dictionary - { - //REST - [GuildBucket.SendEditMessage] = new Bucket("bot:msg:server", 5, 5, BucketTarget.Bot, GlobalBucket.SendEditMessage), - [GuildBucket.DeleteMessage] = new Bucket("dmsg", 5, 1, BucketTarget.Bot), - [GuildBucket.DeleteMessages] = new Bucket("bdmsg", 1, 1, BucketTarget.Bot), - [GuildBucket.ModifyMember] = new Bucket("guild_member", 10, 10, BucketTarget.Bot), - [GuildBucket.Nickname] = new Bucket("guild_member_nick", 1, 1, BucketTarget.Bot) - }.ToImmutableDictionary(); - - //Client-Only - _channelLimits = new Dictionary - { - //REST - [ChannelBucket.SendEditMessage] = new Bucket("msg", 10, 10, BucketTarget.Client, GlobalBucket.SendEditMessage), - }.ToImmutableDictionary(); - } - - public static Bucket GetBucketInfo(GlobalBucket bucket) => _globalLimits[bucket]; - public static Bucket GetBucketInfo(GuildBucket bucket) => _guildLimits[bucket]; - public static Bucket GetBucketInfo(ChannelBucket bucket) => _channelLimits[bucket]; - + public RequestQueue() { _lock = new SemaphoreSlim(1, 1); - _globalBuckets = new RequestQueueBucket[_globalLimits.Count]; - foreach (var pair in _globalLimits) - { - //var target = _globalLimits[pair.Key].Target; - //if (target == BucketTarget.Both || (target == BucketTarget.Bot && isBot) || (target == BucketTarget.Client && !isBot)) - _globalBuckets[(int)pair.Key] = CreateBucket(pair.Value); - } - - _guildBuckets = new ConcurrentDictionary[_guildLimits.Count]; - for (int i = 0; i < _guildLimits.Count; i++) - { - //var target = _guildLimits[(GuildBucket)i].Target; - //if (target == BucketTarget.Both || (target == BucketTarget.Bot && isBot) || (target == BucketTarget.Client && !isBot)) - _guildBuckets[i] = new ConcurrentDictionary(); - } - - _channelBuckets = new ConcurrentDictionary[_channelLimits.Count]; - for (int i = 0; i < _channelLimits.Count; i++) - { - //var target = _channelLimits[(GuildBucket)i].Target; - //if (target == BucketTarget.Both || (target == BucketTarget.Bot && isBot) || (target == BucketTarget.Client && !isBot)) - _channelBuckets[i] = new ConcurrentDictionary(); - } - _clearToken = new CancellationTokenSource(); _cancelToken = CancellationToken.None; _parentToken = CancellationToken.None; + + _buckets = new ConcurrentDictionary(); } public async Task SetCancelTokenAsync(CancellationToken cancelToken) { @@ -107,63 +37,29 @@ namespace Discord.Net.Queue finally { _lock.Release(); } } - public async Task SendAsync(RestRequest request, BucketGroup group, int bucketId, ulong objId) + public async Task SendAsync(RestRequest request) { request.CancelToken = _cancelToken; - var bucket = GetBucket(group, bucketId, objId); + var bucket = GetOrCreateBucket(request.Options.BucketId); return await bucket.SendAsync(request).ConfigureAwait(false); } - public async Task SendAsync(WebSocketRequest request, BucketGroup group, int bucketId, ulong objId) + public async Task SendAsync(WebSocketRequest request) { request.CancelToken = _cancelToken; - var bucket = GetBucket(group, bucketId, objId); + var bucket = GetOrCreateBucket(request.Options.BucketId); return await bucket.SendAsync(request).ConfigureAwait(false); } - private RequestQueueBucket CreateBucket(Bucket def) + private RequestQueueBucket GetOrCreateBucket(string id) { - var parent = def.Parent != null ? GetGlobalBucket(def.Parent.Value) : null; - return new RequestQueueBucket(this, def, parent); + return new RequestQueueBucket(this, id, null); } - public void DestroyGuildBucket(GuildBucket type, ulong guildId) - { - //Assume this object is locked - RequestQueueBucket bucket; - _guildBuckets[(int)type].TryRemove(guildId, out bucket); - } - public void DestroyChannelBucket(ChannelBucket type, ulong channelId) + public void DestroyBucket(string id) { //Assume this object is locked RequestQueueBucket bucket; - _channelBuckets[(int)type].TryRemove(channelId, out bucket); - } - - private RequestQueueBucket GetBucket(BucketGroup group, int bucketId, ulong objId) - { - switch (group) - { - case BucketGroup.Global: - return GetGlobalBucket((GlobalBucket)bucketId); - case BucketGroup.Guild: - return GetGuildBucket((GuildBucket)bucketId, objId); - case BucketGroup.Channel: - return GetChannelBucket((ChannelBucket)bucketId, objId); - default: - throw new ArgumentException($"Unknown bucket group: {group}", nameof(group)); - } - } - private RequestQueueBucket GetGlobalBucket(GlobalBucket type) - { - return _globalBuckets[(int)type]; - } - private RequestQueueBucket GetGuildBucket(GuildBucket type, ulong guildId) - { - return _guildBuckets[(int)type].GetOrAdd(guildId, _ => CreateBucket(_guildLimits[type])); - } - private RequestQueueBucket GetChannelBucket(ChannelBucket type, ulong channelId) - { - return _channelBuckets[(int)type].GetOrAdd(channelId, _ => CreateBucket(_channelLimits[type])); + _buckets.TryRemove(id, out bucket); } public async Task ClearAsync() @@ -181,9 +77,9 @@ namespace Discord.Net.Queue finally { _lock.Release(); } } - internal async Task RaiseRateLimitTriggered(string id, Bucket bucket, int millis) + internal async Task RaiseRateLimitTriggered(string id, RequestQueueBucket bucket, int? millis) { - await RateLimitTriggered.Invoke(id, bucket, millis).ConfigureAwait(false); + await RateLimitTriggered(id, bucket, millis).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs index 2ec9a9e02..00668c315 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - internal class RequestQueueBucket + public class RequestQueueBucket { private readonly RequestQueue _queue; private readonly SemaphoreSlim _semaphore; @@ -15,16 +15,15 @@ namespace Discord.Net.Queue private int _pauseEndTick; private TaskCompletionSource _resumeNotifier; - public Bucket Definition { get; } + public string Id { get; } public RequestQueueBucket Parent { get; } - public Task _resetTask { get; } + public int WindowSeconds { get; } - public RequestQueueBucket(RequestQueue queue, Bucket definition, RequestQueueBucket parent = null) + public RequestQueueBucket(RequestQueue queue, string id, RequestQueueBucket parent = null) { _queue = queue; - Definition = definition; - if (definition.WindowCount != 0) - _semaphore = new SemaphoreSlim(definition.WindowCount, definition.WindowCount); + Id = id; + _semaphore = new SemaphoreSlim(5, 5); Parent = parent; _pauseLock = new object(); @@ -44,12 +43,8 @@ namespace Discord.Net.Queue { //When a 429 occurs, we drop all our locks. //This is generally safe though since 429s actually occuring should be very rare. - RequestQueueBucket bucket; - bool success = FindBucket(ex.BucketId, out bucket); - - await _queue.RaiseRateLimitTriggered(ex.BucketId, success ? bucket.Definition : null, ex.RetryAfterMilliseconds).ConfigureAwait(false); - - bucket.Pause(ex.RetryAfterMilliseconds); + await _queue.RaiseRateLimitTriggered(Id, this, ex.RetryAfterMilliseconds).ConfigureAwait(false); + Pause(ex.RetryAfterMilliseconds); } } } @@ -107,24 +102,7 @@ namespace Discord.Net.Queue QueueExitAsync(); } } - - private bool FindBucket(string id, out RequestQueueBucket bucket) - { - //Keep going up until we find a bucket with matching id or we're at the topmost bucket - if (Definition.Id == id) - { - bucket = this; - return true; - } - else if (Parent == null) - { - bucket = this; - return false; - } - else - return Parent.FindBucket(id, out bucket); - } - + private void Pause(int milliseconds) { lock (_pauseLock) @@ -151,13 +129,22 @@ namespace Discord.Net.Queue int millis = unchecked(endTick.Value - Environment.TickCount); if (millis <= 0 || !await _semaphore.WaitAsync(millis).ConfigureAwait(false)) throw new TimeoutException(); + + if (!await _semaphore.WaitAsync(0)) + { + await _queue.RaiseRateLimitTriggered(Id, this, null).ConfigureAwait(false); + + millis = unchecked(endTick.Value - Environment.TickCount); + if (millis <= 0 || !await _semaphore.WaitAsync(millis).ConfigureAwait(false)) + throw new TimeoutException(); + } } else await _semaphore.WaitAsync().ConfigureAwait(false); } private async Task QueueExitAsync() { - await Task.Delay(Definition.WindowSeconds * 1000).ConfigureAwait(false); + await Task.Delay(WindowSeconds * 1000).ConfigureAwait(false); _semaphore.Release(); } } diff --git a/src/Discord.Net.Core/Net/Queue/IQueuedRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs similarity index 100% rename from src/Discord.Net.Core/Net/Queue/IQueuedRequest.cs rename to src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs diff --git a/src/Discord.Net.Core/Net/Queue/Requests/JsonRestRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/JsonRestRequest.cs new file mode 100644 index 000000000..d715b790c --- /dev/null +++ b/src/Discord.Net.Core/Net/Queue/Requests/JsonRestRequest.cs @@ -0,0 +1,22 @@ +using Discord.Net.Rest; +using System.IO; +using System.Threading.Tasks; + +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).ConfigureAwait(false); + } + } +} diff --git a/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs new file mode 100644 index 000000000..f30969e43 --- /dev/null +++ b/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs @@ -0,0 +1,24 @@ +using Discord.Net.Rest; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Discord.Net.Queue +{ + public class MultipartRestRequest : RestRequest + { + public IReadOnlyDictionary MultipartParams { get; } + + 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).ConfigureAwait(false); + } + } +} diff --git a/src/Discord.Net.Core/Net/Queue/Requests/RestRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/RestRequest.cs new file mode 100644 index 000000000..655a79567 --- /dev/null +++ b/src/Discord.Net.Core/Net/Queue/Requests/RestRequest.cs @@ -0,0 +1,36 @@ +using Discord.Net.Rest; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.Queue +{ + public class RestRequest : IQueuedRequest + { + public IRestClient Client { get; } + public string Method { get; } + public string Endpoint { get; } + public int? TimeoutTick { get; } + public TaskCompletionSource Promise { get; } + public RequestOptions Options { get; } + public CancellationToken CancelToken { get; internal set; } + + public RestRequest(IRestClient client, string method, string endpoint, RequestOptions options) + { + Preconditions.NotNull(options, nameof(options)); + + Client = client; + Method = method; + Endpoint = endpoint; + Options = options; + TimeoutTick = options.Timeout.HasValue ? (int?)unchecked(Environment.TickCount + options.Timeout.Value) : null; + Promise = new TaskCompletionSource(); + } + + public virtual async Task SendAsync() + { + return await Client.SendAsync(Method, Endpoint, Options).ConfigureAwait(false); + } + } +} diff --git a/src/Discord.Net.Core/Net/Queue/WebSocketRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/WebSocketRequest.cs similarity index 82% rename from src/Discord.Net.Core/Net/Queue/WebSocketRequest.cs rename to src/Discord.Net.Core/Net/Queue/Requests/WebSocketRequest.cs index 83f40a4d6..796517c85 100644 --- a/src/Discord.Net.Core/Net/Queue/WebSocketRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/Requests/WebSocketRequest.cs @@ -13,16 +13,17 @@ namespace Discord.Net.Queue public bool IsText { get; } public int? TimeoutTick { get; } public TaskCompletionSource Promise { get; } - public CancellationToken CancelToken { get; set; } - + public RequestOptions Options { get; } + public CancellationToken CancelToken { get; internal set; } + public WebSocketRequest(IWebSocketClient client, byte[] data, bool isText, RequestOptions options) { - if (options == null) - options = RequestOptions.Default; + Preconditions.NotNull(options, nameof(options)); Client = client; Data = data; IsText = isText; + Options = options; TimeoutTick = options.Timeout.HasValue ? (int?)unchecked(Environment.TickCount + options.Timeout.Value) : null; Promise = new TaskCompletionSource(); } diff --git a/src/Discord.Net.Core/Net/Queue/RestRequest.cs b/src/Discord.Net.Core/Net/Queue/RestRequest.cs deleted file mode 100644 index 4be4c746c..000000000 --- a/src/Discord.Net.Core/Net/Queue/RestRequest.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Discord.Net.Rest; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Discord.Net.Queue -{ - public class RestRequest : IQueuedRequest - { - public IRestClient Client { get; } - public string Method { get; } - public string Endpoint { get; } - public string Json { get; } - public bool HeaderOnly { get; } - public int? TimeoutTick { get; } - public IReadOnlyDictionary MultipartParams { get; } - public TaskCompletionSource Promise { get; } - public CancellationToken CancelToken { get; set; } - - public bool IsMultipart => MultipartParams != null; - - public RestRequest(IRestClient client, string method, string endpoint, string json, bool headerOnly, RequestOptions options) - : this(client, method, endpoint, headerOnly, options) - { - Json = json; - } - - public RestRequest(IRestClient client, string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly, RequestOptions options) - : this(client, method, endpoint, headerOnly, options) - { - MultipartParams = multipartParams; - } - - private RestRequest(IRestClient client, string method, string endpoint, bool headerOnly, RequestOptions options) - { - if (options == null) - options = RequestOptions.Default; - - Client = client; - Method = method; - Endpoint = endpoint; - Json = null; - MultipartParams = null; - HeaderOnly = headerOnly; - TimeoutTick = options.Timeout.HasValue ? (int?)unchecked(Environment.TickCount + options.Timeout.Value) : null; - Promise = new TaskCompletionSource(); - } - - public async Task SendAsync() - { - if (IsMultipart) - return await Client.SendAsync(Method, Endpoint, MultipartParams, HeaderOnly).ConfigureAwait(false); - else if (Json != null) - return await Client.SendAsync(Method, Endpoint, Json, HeaderOnly).ConfigureAwait(false); - else - return await Client.SendAsync(Method, Endpoint, HeaderOnly).ConfigureAwait(false); - } - } -} diff --git a/src/Discord.Net.Core/Net/RateLimitException.cs b/src/Discord.Net.Core/Net/RateLimitException.cs index ff594155a..cb0ca7f28 100644 --- a/src/Discord.Net.Core/Net/RateLimitException.cs +++ b/src/Discord.Net.Core/Net/RateLimitException.cs @@ -4,13 +4,13 @@ namespace Discord.Net { public class HttpRateLimitException : HttpException { - public string BucketId { get; } + public string Id { get; } public int RetryAfterMilliseconds { get; } public HttpRateLimitException(string bucketId, int retryAfterMilliseconds, string reason) : base((HttpStatusCode)429, reason) { - BucketId = bucketId; + Id = bucketId; RetryAfterMilliseconds = retryAfterMilliseconds; } } diff --git a/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs b/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs index bcad2ece4..b06df37b8 100644 --- a/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs @@ -67,22 +67,22 @@ namespace Discord.Net.Rest _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; } - public async Task SendAsync(string method, string endpoint, bool headerOnly = false) + public async Task SendAsync(string method, string endpoint, RequestOptions options) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) - return await SendInternalAsync(restRequest, headerOnly).ConfigureAwait(false); + return await SendInternalAsync(restRequest, options).ConfigureAwait(false); } - public async Task SendAsync(string method, string endpoint, string json, bool headerOnly = false) + public async Task SendAsync(string method, string endpoint, string json, RequestOptions options) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json"); - return await SendInternalAsync(restRequest, headerOnly).ConfigureAwait(false); + return await SendInternalAsync(restRequest, options).ConfigureAwait(false); } } - public async Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly = false) + public async Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, RequestOptions options) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) @@ -110,11 +110,11 @@ namespace Discord.Net.Rest } } restRequest.Content = content; - return await SendInternalAsync(restRequest, headerOnly).ConfigureAwait(false); + return await SendInternalAsync(restRequest, options).ConfigureAwait(false); } } - private async Task SendInternalAsync(HttpRequestMessage request, bool headerOnly) + private async Task SendInternalAsync(HttpRequestMessage request, RequestOptions options) { while (true) { @@ -154,7 +154,7 @@ namespace Discord.Net.Rest throw new HttpException(response.StatusCode, reason); } - if (headerOnly) + if (options.HeaderOnly) return null; else return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.Core/Net/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs index 57b5f91ca..7f90060b6 100644 --- a/src/Discord.Net.Core/Net/Rest/IRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/IRestClient.cs @@ -11,8 +11,8 @@ namespace Discord.Net.Rest void SetHeader(string key, string value); void SetCancelToken(CancellationToken cancelToken); - Task SendAsync(string method, string endpoint, bool headerOnly = false); - Task SendAsync(string method, string endpoint, string json, bool headerOnly = false); - Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly = false); + Task SendAsync(string method, string endpoint, RequestOptions options); + Task SendAsync(string method, string endpoint, string json, RequestOptions options); + Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, RequestOptions options); } } diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 242642d56..452b75444 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -6,10 +6,24 @@ /// 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 string BucketId { get; set; } + public bool HeaderOnly { get; internal set; } + + internal bool IgnoreState { get; set; } + + public static RequestOptions CreateOrClone(RequestOptions options) + { + if (options == null) + return new RequestOptions(); + else + return options.Clone(); + } public RequestOptions() { Timeout = 30000; } + + public RequestOptions Clone() => MemberwiseClone() as RequestOptions; } } diff --git a/src/Discord.Net.Rest/DiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs similarity index 96% rename from src/Discord.Net.Rest/DiscordClient.cs rename to src/Discord.Net.Rest/BaseDiscordClient.cs index 5bf0f2c89..b6ea634d7 100644 --- a/src/Discord.Net.Rest/DiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -8,7 +8,7 @@ using System.Collections.Immutable; namespace Discord.Rest { - public abstract class DiscordClient : IDiscordClient + public abstract class BaseDiscordClient : IDiscordClient { public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } private readonly AsyncEvent> _logEvent = new AsyncEvent>(); @@ -29,7 +29,7 @@ namespace Discord.Rest public ISelfUser CurrentUser { get; protected set; } /// Creates a new REST-only discord client. - internal DiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) + internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) { ApiClient = client; LogManager = new LogManager(config.LogLevel); @@ -73,6 +73,7 @@ namespace Discord.Rest try { + await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); await OnLoginAsync(tokenType, token).ConfigureAwait(false); LoginState = LoginState.LoggedIn; } diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 07868182d..02ccb2c5b 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -10,13 +10,13 @@ namespace Discord.Rest internal static class ClientHelper { //Applications - public static async Task GetApplicationInfoAsync(DiscordClient client) + public static async Task GetApplicationInfoAsync(BaseDiscordClient client) { var model = await client.ApiClient.GetMyApplicationAsync().ConfigureAwait(false); return RestApplication.Create(client, model); } - public static async Task GetChannelAsync(DiscordClient client, + public static async Task GetChannelAsync(BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetChannelAsync(id).ConfigureAwait(false); @@ -24,19 +24,19 @@ namespace Discord.Rest return RestChannel.Create(client, model); return null; } - public static async Task> GetPrivateChannelsAsync(DiscordClient client) + public static async Task> GetPrivateChannelsAsync(BaseDiscordClient client) { var models = await client.ApiClient.GetMyPrivateChannelsAsync().ConfigureAwait(false); return models.Select(x => RestDMChannel.Create(client, x)).ToImmutableArray(); } - public static async Task> GetConnectionsAsync(DiscordClient client) + public static async Task> GetConnectionsAsync(BaseDiscordClient client) { var models = await client.ApiClient.GetMyConnectionsAsync().ConfigureAwait(false); return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); } - public static async Task GetInviteAsync(DiscordClient client, + public static async Task GetInviteAsync(BaseDiscordClient client, string inviteId) { var model = await client.ApiClient.GetInviteAsync(inviteId).ConfigureAwait(false); @@ -45,7 +45,7 @@ namespace Discord.Rest return null; } - public static async Task GetGuildAsync(DiscordClient client, + public static async Task GetGuildAsync(BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetGuildAsync(id).ConfigureAwait(false); @@ -53,7 +53,7 @@ namespace Discord.Rest return RestGuild.Create(client, model); return null; } - public static async Task GetGuildEmbedAsync(DiscordClient client, + public static async Task GetGuildEmbedAsync(BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetGuildEmbedAsync(id).ConfigureAwait(false); @@ -61,12 +61,12 @@ namespace Discord.Rest return RestGuildEmbed.Create(model); return null; } - public static async Task> GetGuildSummariesAsync(DiscordClient client) + public static async Task> GetGuildSummariesAsync(BaseDiscordClient client) { var models = await client.ApiClient.GetMyGuildsAsync().ConfigureAwait(false); return models.Select(x => RestUserGuild.Create(client, x)).ToImmutableArray(); } - public static async Task> GetGuildsAsync(DiscordClient client) + public static async Task> GetGuildsAsync(BaseDiscordClient client) { var summaryModels = await client.ApiClient.GetMyGuildsAsync().ConfigureAwait(false); var guilds = ImmutableArray.CreateBuilder(summaryModels.Count); @@ -78,7 +78,7 @@ namespace Discord.Rest } return guilds.ToImmutable(); } - public static async Task CreateGuildAsync(DiscordClient client, + public static async Task CreateGuildAsync(BaseDiscordClient client, string name, IVoiceRegion region, Stream jpegIcon = null) { var args = new CreateGuildParams(name, region.Id); @@ -86,7 +86,7 @@ namespace Discord.Rest return RestGuild.Create(client, model); } - public static async Task GetUserAsync(DiscordClient client, + public static async Task GetUserAsync(BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetUserAsync(id).ConfigureAwait(false); @@ -94,7 +94,7 @@ namespace Discord.Rest return RestUser.Create(client, model); return null; } - public static async Task GetUserAsync(DiscordClient client, + public static async Task GetUserAsync(BaseDiscordClient client, string username, string discriminator) { var model = await client.ApiClient.GetUserAsync(username, discriminator).ConfigureAwait(false); @@ -103,12 +103,12 @@ namespace Discord.Rest return null; } - public static async Task> GetVoiceRegionsAsync(DiscordClient client) + public static async Task> GetVoiceRegionsAsync(BaseDiscordClient client) { var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); return models.Select(x => RestVoiceRegion.Create(client, x)).ToImmutableArray(); } - public static async Task GetVoiceRegionAsync(DiscordClient client, + public static async Task GetVoiceRegionAsync(BaseDiscordClient client, string id) { var models = await client.ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 8bc3ca9c3..6fa954116 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Discord.Rest { - public class DiscordRestClient : DiscordClient, IDiscordClient + public class DiscordRestClient : BaseDiscordClient, IDiscordClient { public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; @@ -15,10 +15,10 @@ namespace Discord.Rest private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, requestQueue: new RequestQueue()); - protected override async Task OnLoginAsync(TokenType tokenType, string token) + protected override Task OnLoginAsync(TokenType tokenType, string token) { - await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); + return Task.CompletedTask; } /// diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 63f68049f..af8ab3fc0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -12,33 +12,33 @@ namespace Discord.Rest internal static class ChannelHelper { //General - public static async Task GetAsync(IGuildChannel channel, DiscordClient client) + public static async Task GetAsync(IGuildChannel channel, BaseDiscordClient client) { return await client.ApiClient.GetChannelAsync(channel.GuildId, channel.Id).ConfigureAwait(false); } - public static async Task GetAsync(IPrivateChannel channel, DiscordClient client) + public static async Task GetAsync(IPrivateChannel channel, BaseDiscordClient client) { return await client.ApiClient.GetChannelAsync(channel.Id).ConfigureAwait(false); } - public static async Task DeleteAsync(IChannel channel, DiscordClient client) + public static async Task DeleteAsync(IChannel channel, BaseDiscordClient client) { await client.ApiClient.DeleteChannelAsync(channel.Id).ConfigureAwait(false); } - public static async Task ModifyAsync(IGuildChannel channel, DiscordClient client, + public static async Task ModifyAsync(IGuildChannel channel, BaseDiscordClient client, Action func) { var args = new ModifyGuildChannelParams(); func(args); await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); } - public static async Task ModifyAsync(ITextChannel channel, DiscordClient client, + public static async Task ModifyAsync(ITextChannel channel, BaseDiscordClient client, Action func) { var args = new ModifyTextChannelParams(); func(args); await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); } - public static async Task ModifyAsync(IVoiceChannel channel, DiscordClient client, + public static async Task ModifyAsync(IVoiceChannel channel, BaseDiscordClient client, Action func) { var args = new ModifyVoiceChannelParams(); @@ -47,12 +47,12 @@ namespace Discord.Rest } //Invites - public static async Task> GetInvitesAsync(IChannel channel, DiscordClient client) + public static async Task> GetInvitesAsync(IChannel channel, BaseDiscordClient client) { var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); } - public static async Task CreateInviteAsync(IChannel channel, DiscordClient client, + public static async Task CreateInviteAsync(IChannel channel, BaseDiscordClient client, int? maxAge, int? maxUses, bool isTemporary) { var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; @@ -65,13 +65,13 @@ namespace Discord.Rest } //Messages - public static async Task GetMessageAsync(IChannel channel, DiscordClient client, + public static async Task GetMessageAsync(IChannel channel, BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false); return RestMessage.Create(client, model); } - public static IAsyncEnumerable> GetMessagesAsync(IChannel channel, DiscordClient client, + public static IAsyncEnumerable> GetMessagesAsync(IChannel channel, BaseDiscordClient client, ulong? fromMessageId = null, Direction dir = Direction.Before, int limit = DiscordConfig.MaxMessagesPerBatch) { //TODO: Test this with Around direction @@ -102,13 +102,13 @@ namespace Discord.Rest count: (uint)limit ); } - public static async Task> GetPinnedMessagesAsync(IChannel channel, DiscordClient client) + public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client) { var models = await client.ApiClient.GetPinsAsync(channel.Id).ConfigureAwait(false); return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); } - public static async Task SendMessageAsync(IChannel channel, DiscordClient client, + public static async Task SendMessageAsync(IChannel channel, BaseDiscordClient client, string text, bool isTTS) { var args = new CreateMessageParams(text) { IsTTS = isTTS }; @@ -116,14 +116,14 @@ namespace Discord.Rest return RestUserMessage.Create(client, model); } - public static Task SendFileAsync(IChannel channel, DiscordClient client, + public static Task SendFileAsync(IChannel channel, BaseDiscordClient client, string filePath, string text, bool isTTS) { string filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) return SendFileAsync(channel, client, file, filename, text, isTTS); } - public static async Task SendFileAsync(IChannel channel, DiscordClient client, + public static async Task SendFileAsync(IChannel channel, BaseDiscordClient client, Stream stream, string filename, string text, bool isTTS) { var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; @@ -131,7 +131,7 @@ namespace Discord.Rest return RestUserMessage.Create(client, model); } - public static async Task DeleteMessagesAsync(IChannel channel, DiscordClient client, + public static async Task DeleteMessagesAsync(IChannel channel, BaseDiscordClient client, IEnumerable messages) { var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); @@ -139,31 +139,31 @@ namespace Discord.Rest } //Permission Overwrites - public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, + public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, IUser user, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args).ConfigureAwait(false); } - public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, + public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, IRole role, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args).ConfigureAwait(false); } - public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, + public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, IUser user) { await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id).ConfigureAwait(false); } - public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, DiscordClient client, + public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, IRole role) { await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false); } //Users - public static async Task GetUserAsync(IGuildChannel channel, DiscordClient client, + public static async Task GetUserAsync(IGuildChannel channel, BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id); @@ -175,7 +175,7 @@ namespace Discord.Rest return user; } - public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, DiscordClient client, + public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, BaseDiscordClient client, ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch) { return new PagedAsyncEnumerable( @@ -203,7 +203,7 @@ namespace Discord.Rest } //Typing - public static IDisposable EnterTypingState(IChannel channel, DiscordClient client) + public static IDisposable EnterTypingState(IChannel channel, BaseDiscordClient client) { throw new NotImplementedException(); //TODO: Impl } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index ada4008ec..868bc7e37 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -11,11 +11,11 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class RestChannel : RestEntity, IChannel, IUpdateable { - internal RestChannel(DiscordClient discord, ulong id) + internal RestChannel(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal static RestChannel Create(DiscordClient discord, Model model) + internal static RestChannel Create(BaseDiscordClient discord, Model model) { switch (model.Type) { diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 973a001a8..6e7451f22 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -17,13 +17,13 @@ namespace Discord.Rest public IReadOnlyCollection Users => ImmutableArray.Create(CurrentUser, Recipient); - internal RestDMChannel(DiscordClient discord, ulong id, ulong recipientId) + 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(DiscordClient discord, Model 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); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 00be0ca11..0324ba1fe 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -21,11 +21,11 @@ namespace Discord.Rest public IReadOnlyCollection Recipients => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); - internal RestGroupChannel(DiscordClient discord, ulong id) + internal RestGroupChannel(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal new static RestGroupChannel Create(DiscordClient discord, Model model) + internal new static RestGroupChannel Create(BaseDiscordClient discord, Model model) { var entity = new RestGroupChannel(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 4ec79496d..d6a4143d3 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -21,12 +21,12 @@ namespace Discord.Rest public string Name { get; private set; } public int Position { get; private set; } - internal RestGuildChannel(DiscordClient discord, ulong id, ulong guildId) + internal RestGuildChannel(BaseDiscordClient discord, ulong id, ulong guildId) : base(discord, id) { GuildId = guildId; } - internal new static RestGuildChannel Create(DiscordClient discord, Model model) + internal new static RestGuildChannel Create(BaseDiscordClient discord, Model model) { switch (model.Type) { diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 69917f862..6e15825e0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -16,11 +16,11 @@ namespace Discord.Rest public string Mention => MentionUtils.MentionChannel(Id); - internal RestTextChannel(DiscordClient discord, ulong id, ulong guildId) + internal RestTextChannel(BaseDiscordClient discord, ulong id, ulong guildId) : base(discord, id, guildId) { } - internal new static RestTextChannel Create(DiscordClient discord, Model model) + internal new static RestTextChannel Create(BaseDiscordClient discord, Model model) { var entity = new RestTextChannel(discord, model.Id, model.GuildId.Value); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 2b23eaad7..4653ee381 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -16,11 +16,11 @@ namespace Discord.Rest public int Bitrate { get; private set; } public int UserLimit { get; private set; } - internal RestVoiceChannel(DiscordClient discord, ulong id, ulong guildId) + internal RestVoiceChannel(BaseDiscordClient discord, ulong id, ulong guildId) : base(discord, id, guildId) { } - internal new static RestVoiceChannel Create(DiscordClient discord, Model model) + internal new static RestVoiceChannel Create(BaseDiscordClient discord, Model model) { var entity = new RestVoiceChannel(discord, model.Id, model.GuildId.Value); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index e88c38504..a00f6243f 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -13,7 +13,7 @@ namespace Discord.Rest internal static class GuildHelper { //General - public static async Task ModifyAsync(IGuild guild, DiscordClient client, + public static async Task ModifyAsync(IGuild guild, BaseDiscordClient client, Action func) { if (func == null) throw new NullReferenceException(nameof(func)); @@ -28,7 +28,7 @@ namespace Discord.Rest return await client.ApiClient.ModifyGuildAsync(guild.Id, args).ConfigureAwait(false); } - public static async Task ModifyEmbedAsync(IGuild guild, DiscordClient client, + public static async Task ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, Action func) { if (func == null) throw new NullReferenceException(nameof(func)); @@ -37,46 +37,46 @@ namespace Discord.Rest func(args); return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args).ConfigureAwait(false); } - public static async Task ModifyChannelsAsync(IGuild guild, DiscordClient client, + public static async Task ModifyChannelsAsync(IGuild guild, BaseDiscordClient client, IEnumerable args) { await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args).ConfigureAwait(false); } - public static async Task> ModifyRolesAsync(IGuild guild, DiscordClient client, + public static async Task> ModifyRolesAsync(IGuild guild, BaseDiscordClient client, IEnumerable args) { return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args).ConfigureAwait(false); } - public static async Task LeaveAsync(IGuild guild, DiscordClient client) + public static async Task LeaveAsync(IGuild guild, BaseDiscordClient client) { await client.ApiClient.LeaveGuildAsync(guild.Id).ConfigureAwait(false); } - public static async Task DeleteAsync(IGuild guild, DiscordClient client) + public static async Task DeleteAsync(IGuild guild, BaseDiscordClient client) { await client.ApiClient.DeleteGuildAsync(guild.Id).ConfigureAwait(false); } //Bans - public static async Task> GetBansAsync(IGuild guild, DiscordClient client) + public static async Task> GetBansAsync(IGuild guild, BaseDiscordClient client) { var models = await client.ApiClient.GetGuildBansAsync(guild.Id); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); } - public static async Task AddBanAsync(IGuild guild, DiscordClient client, + public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, int pruneDays) { var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args); } - public static async Task RemoveBanAsync(IGuild guild, DiscordClient client, + public static async Task RemoveBanAsync(IGuild guild, BaseDiscordClient client, ulong userId) { await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId); } //Channels - public static async Task GetChannelAsync(IGuild guild, DiscordClient client, + public static async Task GetChannelAsync(IGuild guild, BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false); @@ -84,12 +84,12 @@ namespace Discord.Rest return RestGuildChannel.Create(client, model); return null; } - public static async Task> GetChannelsAsync(IGuild guild, DiscordClient client) + public static async Task> GetChannelsAsync(IGuild guild, BaseDiscordClient client) { var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); return models.Select(x => RestGuildChannel.Create(client, x)).ToImmutableArray(); } - public static async Task CreateTextChannelAsync(IGuild guild, DiscordClient client, + public static async Task CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, string name) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -98,7 +98,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); return RestTextChannel.Create(client, model); } - public static async Task CreateVoiceChannelAsync(IGuild guild, DiscordClient client, + public static async Task CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, string name) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -109,12 +109,12 @@ namespace Discord.Rest } //Integrations - public static async Task> GetIntegrationsAsync(IGuild guild, DiscordClient client) + public static async Task> GetIntegrationsAsync(IGuild guild, BaseDiscordClient client) { var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id).ConfigureAwait(false); return models.Select(x => RestGuildIntegration.Create(client, x)).ToImmutableArray(); } - public static async Task CreateIntegrationAsync(IGuild guild, DiscordClient client, + public static async Task CreateIntegrationAsync(IGuild guild, BaseDiscordClient client, ulong id, string type) { var args = new CreateGuildIntegrationParams(id, type); @@ -123,14 +123,14 @@ namespace Discord.Rest } //Invites - public static async Task> GetInvitesAsync(IGuild guild, DiscordClient client) + public static async Task> GetInvitesAsync(IGuild guild, BaseDiscordClient client) { var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id).ConfigureAwait(false); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); } //Roles - public static async Task CreateRoleAsync(IGuild guild, DiscordClient client, + public static async Task CreateRoleAsync(IGuild guild, BaseDiscordClient client, string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -150,7 +150,7 @@ namespace Discord.Rest } //Users - public static async Task GetUserAsync(IGuild guild, DiscordClient client, + public static async Task GetUserAsync(IGuild guild, BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false); @@ -158,17 +158,17 @@ namespace Discord.Rest return RestGuildUser.Create(client, model); return null; } - public static async Task GetCurrentUserAsync(IGuild guild, DiscordClient client) + public static async Task GetCurrentUserAsync(IGuild guild, BaseDiscordClient client) { return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false); } - public static async Task> GetUsersAsync(IGuild guild, DiscordClient client) + public static async Task> GetUsersAsync(IGuild guild, BaseDiscordClient client) { var args = new GetGuildMembersParams(); var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false); return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); } - public static async Task PruneUsersAsync(IGuild guild, DiscordClient client, + public static async Task PruneUsersAsync(IGuild guild, BaseDiscordClient client, int days = 30, bool simulate = false) { var args = new GuildPruneParams(days); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs index 0d919bf0f..d28856d6d 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs @@ -14,7 +14,7 @@ namespace Discord.Rest User = user; Reason = reason; } - internal static RestBan Create(DiscordClient client, Model model) + internal static RestBan Create(BaseDiscordClient client, Model model) { return new RestBan(RestUser.Create(client, model.User), model.Reason); } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 48ddfa607..601329b5c 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -39,11 +39,11 @@ namespace Discord.Rest public IReadOnlyCollection Emojis => _emojis; public IReadOnlyCollection Features => _features; - internal RestGuild(DiscordClient client, ulong id) + internal RestGuild(BaseDiscordClient client, ulong id) : base(client, id) { } - internal static RestGuild Create(DiscordClient discord, Model model) + internal static RestGuild Create(BaseDiscordClient discord, Model model) { var entity = new RestGuild(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs index bb40a4da4..79b75170b 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs @@ -25,11 +25,11 @@ namespace Discord.Rest public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); - internal RestGuildIntegration(DiscordClient discord, ulong id) + internal RestGuildIntegration(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal static RestGuildIntegration Create(DiscordClient discord, Model model) + internal static RestGuildIntegration Create(BaseDiscordClient discord, Model model) { var entity = new RestGuildIntegration(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index fab07ee33..d222c2d1d 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -15,11 +15,11 @@ namespace Discord.Rest public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); - internal RestUserGuild(DiscordClient discord, ulong id) + internal RestUserGuild(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal static RestUserGuild Create(DiscordClient discord, Model model) + internal static RestUserGuild Create(BaseDiscordClient discord, Model model) { var entity = new RestUserGuild(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs index 465b2e185..47fd2cd19 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs @@ -13,11 +13,11 @@ namespace Discord public string SampleHostname { get; private set; } public int SamplePort { get; private set; } - internal RestVoiceRegion(DiscordClient client, string id) + internal RestVoiceRegion(BaseDiscordClient client, string id) : base(client, id) { } - internal static RestVoiceRegion Create(DiscordClient client, Model model) + internal static RestVoiceRegion Create(BaseDiscordClient client, Model model) { var entity = new RestVoiceRegion(client, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs index 3ede5d042..23e0ddab7 100644 --- a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs +++ b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs @@ -5,15 +5,15 @@ namespace Discord.Rest { internal static class InviteHelper { - public static async Task GetAsync(IInvite invite, DiscordClient client) + public static async Task GetAsync(IInvite invite, BaseDiscordClient client) { return await client.ApiClient.GetInviteAsync(invite.Code).ConfigureAwait(false); } - public static async Task AcceptAsync(IInvite invite, DiscordClient client) + public static async Task AcceptAsync(IInvite invite, BaseDiscordClient client) { await client.ApiClient.AcceptInviteAsync(invite.Code).ConfigureAwait(false); } - public static async Task DeleteAsync(IInvite invite, DiscordClient client) + public static async Task DeleteAsync(IInvite invite, BaseDiscordClient client) { await client.ApiClient.DeleteInviteAsync(invite.Code).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 3cf11b4c6..6beabacb1 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -16,11 +16,11 @@ namespace Discord.Rest public string Code => Id; public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; - internal RestInvite(DiscordClient discord, string id) + internal RestInvite(BaseDiscordClient discord, string id) : base(discord, id) { } - internal static RestInvite Create(DiscordClient discord, Model model) + internal static RestInvite Create(BaseDiscordClient discord, Model model) { var entity = new RestInvite(discord, model.Code); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 25ce2fb24..131211a39 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -18,11 +18,11 @@ namespace Discord.Rest public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); - internal RestInviteMetadata(DiscordClient discord, string id) + internal RestInviteMetadata(BaseDiscordClient discord, string id) : base(discord, id) { } - internal static RestInviteMetadata Create(DiscordClient discord, Model model) + internal static RestInviteMetadata Create(BaseDiscordClient discord, Model model) { var entity = new RestInviteMetadata(discord, model.Code); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index f1f0e8461..28338635b 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -6,26 +6,26 @@ namespace Discord.Rest { internal static class MessageHelper { - public static async Task GetAsync(IMessage msg, DiscordClient client) + public static async Task GetAsync(IMessage msg, BaseDiscordClient client) { await client.ApiClient.GetChannelMessageAsync(msg.ChannelId, msg.Id); } - public static async Task ModifyAsync(IMessage msg, DiscordClient client, Action func) + public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func) { var args = new ModifyMessageParams(); func(args); await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args); } - public static async Task DeleteAsync(IMessage msg, DiscordClient client) + public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client) { await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id); } - public static async Task PinAsync(IMessage msg, DiscordClient client) + public static async Task PinAsync(IMessage msg, BaseDiscordClient client) { await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id); } - public static async Task UnpinAsync(IMessage msg, DiscordClient client) + public static async Task UnpinAsync(IMessage msg, BaseDiscordClient client) { await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 33ce9c8f0..660cdadf3 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -29,12 +29,12 @@ namespace Discord.Rest public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal RestMessage(DiscordClient discord, ulong id, ulong channelId) + internal RestMessage(BaseDiscordClient discord, ulong id, ulong channelId) : base(discord, id) { ChannelId = channelId; } - internal static RestMessage Create(DiscordClient discord, Model model) + internal static RestMessage Create(BaseDiscordClient discord, Model model) { if (model.Type == MessageType.Default) return RestUserMessage.Create(discord, model); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index 837fd158f..9cef3f479 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -8,11 +8,11 @@ namespace Discord.Rest { public MessageType Type { get; private set; } - internal RestSystemMessage(DiscordClient discord, ulong id, ulong channelId) + internal RestSystemMessage(BaseDiscordClient discord, ulong id, ulong channelId) : base(discord, id, channelId) { } - internal new static RestSystemMessage Create(DiscordClient discord, Model model) + internal new static RestSystemMessage Create(BaseDiscordClient discord, Model model) { var entity = new RestSystemMessage(discord, model.Id, model.ChannelId); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index b81fbf17f..824de3c79 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -30,11 +30,11 @@ namespace Discord.Rest public override IReadOnlyCollection MentionedRoles => _mentionedRoles; public override IReadOnlyCollection MentionedUsers => _mentionedUsers; - internal RestUserMessage(DiscordClient discord, ulong id, ulong channelId) + internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId) : base(discord, id, channelId) { } - internal new static RestUserMessage Create(DiscordClient discord, Model model) + internal new static RestUserMessage Create(BaseDiscordClient discord, Model model) { var entity = new RestUserMessage(discord, model.Id, model.ChannelId); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index 035e6c212..e0b119381 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -17,11 +17,11 @@ namespace Discord.Rest public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId); - internal RestApplication(DiscordClient discord, ulong id) + internal RestApplication(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal static RestApplication Create(DiscordClient discord, Model model) + internal static RestApplication Create(BaseDiscordClient discord, Model model) { var entity = new RestApplication(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/RestEntity.cs b/src/Discord.Net.Rest/Entities/RestEntity.cs index d5bad8147..adf7b6921 100644 --- a/src/Discord.Net.Rest/Entities/RestEntity.cs +++ b/src/Discord.Net.Rest/Entities/RestEntity.cs @@ -5,10 +5,10 @@ namespace Discord.Rest public abstract class RestEntity : IEntity where T : IEquatable { - public DiscordClient Discord { get; } + public BaseDiscordClient Discord { get; } public T Id { get; } - internal RestEntity(DiscordClient discord, T id) + internal RestEntity(BaseDiscordClient discord, T id) { Discord = discord; Id = id; diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index b0f3255f4..35d4a14f5 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -21,11 +21,11 @@ namespace Discord.Rest public bool IsEveryone => Id == Guild.Id; public string Mention => MentionUtils.MentionRole(Id); - internal RestRole(DiscordClient discord, ulong id) + internal RestRole(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal static RestRole Create(DiscordClient discord, Model model) + internal static RestRole Create(BaseDiscordClient discord, Model model) { var entity = new RestRole(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs index dc66e9288..c9438f10d 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -7,11 +7,11 @@ namespace Discord.Rest internal static class RoleHelper { //General - public static async Task DeleteAsync(IRole role, DiscordClient client) + public static async Task DeleteAsync(IRole role, BaseDiscordClient client) { await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id).ConfigureAwait(false); } - public static async Task ModifyAsync(IRole role, DiscordClient client, + public static async Task ModifyAsync(IRole role, BaseDiscordClient client, Action func) { var args = new ModifyGuildRoleParams(); diff --git a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs index a9ef2907d..951bd2e7c 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs @@ -6,11 +6,11 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestGroupUser : RestUser, IGroupUser { - internal RestGroupUser(DiscordClient discord, ulong id) + internal RestGroupUser(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal new static RestGroupUser Create(DiscordClient discord, Model model) + internal new static RestGroupUser Create(BaseDiscordClient discord, Model model) { var entity = new RestGroupUser(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 489672e31..2fe25b416 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -21,11 +21,11 @@ namespace Discord.Rest public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); - internal RestGuildUser(DiscordClient discord, ulong id) + internal RestGuildUser(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal static RestGuildUser Create(DiscordClient discord, Model model) + internal static RestGuildUser Create(BaseDiscordClient discord, Model model) { var entity = new RestGuildUser(discord, model.User.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs index 115db9ac5..17c24e7d2 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -13,11 +13,11 @@ namespace Discord.Rest public bool IsVerified { get; private set; } public bool IsMfaEnabled { get; private set; } - internal RestSelfUser(DiscordClient discord, ulong id) + internal RestSelfUser(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal new static RestSelfUser Create(DiscordClient discord, Model model) + internal new static RestSelfUser Create(BaseDiscordClient discord, Model model) { var entity = new RestSelfUser(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 492a18bd4..6a42de397 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -18,11 +18,11 @@ namespace Discord.Rest public virtual Game? Game => null; public virtual UserStatus Status => UserStatus.Unknown; - internal RestUser(DiscordClient discord, ulong id) + internal RestUser(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal static RestUser Create(DiscordClient discord, Model model) + internal static RestUser Create(BaseDiscordClient discord, Model model) { var entity = new RestUser(discord, model.Id); entity.Update(model); diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index da8e57fad..ddf8055d9 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -8,22 +8,22 @@ namespace Discord.Rest { internal static class UserHelper { - public static async Task GetAsync(IUser user, DiscordClient client) + public static async Task GetAsync(IUser user, BaseDiscordClient client) { return await client.ApiClient.GetUserAsync(user.Id); } - public static async Task GetAsync(ISelfUser user, DiscordClient client) + public static async Task GetAsync(ISelfUser user, BaseDiscordClient client) { var model = await client.ApiClient.GetMyUserAsync(); if (model.Id != user.Id) throw new InvalidOperationException("Unable to update this object using a different token."); return model; } - public static async Task GetAsync(IGuildUser user, DiscordClient client) + public static async Task GetAsync(IGuildUser user, BaseDiscordClient client) { return await client.ApiClient.GetGuildMemberAsync(user.GuildId, user.Id); } - public static async Task ModifyAsync(ISelfUser user, DiscordClient client, Action func) + public static async Task ModifyAsync(ISelfUser user, BaseDiscordClient client, Action func) { if (user.Id != client.CurrentUser.Id) throw new InvalidOperationException("Unable to modify this object using a different token."); @@ -32,19 +32,19 @@ namespace Discord.Rest func(args); await client.ApiClient.ModifySelfAsync(args); } - public static async Task ModifyAsync(IGuildUser user, DiscordClient client, Action func) + public static async Task ModifyAsync(IGuildUser user, BaseDiscordClient client, Action func) { var args = new ModifyGuildMemberParams(); func(args); await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args); } - public static async Task KickAsync(IGuildUser user, DiscordClient client) + public static async Task KickAsync(IGuildUser user, BaseDiscordClient client) { await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); } - public static async Task CreateDMChannelAsync(IUser user, DiscordClient client) + public static async Task CreateDMChannelAsync(IUser user, BaseDiscordClient client) { var args = new CreateDMChannelParams(user.Id); return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index 666d92105..d413fd815 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -206,19 +206,16 @@ namespace Discord.API } //Core - public Task SendRpcAsync(string cmd, object payload, GlobalBucket bucket = GlobalBucket.GeneralRpc, - Optional evt = default(Optional), bool ignoreState = false, RequestOptions options = null) + public async Task SendRpcAsync(string cmd, object payload, Optional evt = default(Optional), RequestOptions options = null) where TResponse : class - => SendRpcAsyncInternal(cmd, payload, BucketGroup.Global, (int)bucket, 0, evt, ignoreState, options); - public Task SendRpcAsync(string cmd, object payload, GuildBucket bucket, ulong guildId, - Optional evt = default(Optional), bool ignoreState = false, RequestOptions options = null) - where TResponse : class - => SendRpcAsyncInternal(cmd, payload, BucketGroup.Guild, (int)bucket, guildId, evt, ignoreState, options); - private async Task SendRpcAsyncInternal(string cmd, object payload, BucketGroup group, int bucketId, ulong guildId, - Optional evt, bool ignoreState, RequestOptions options) + { + options.IgnoreState = false; + return await SendRpcAsyncInternal(cmd, payload, evt, options).ConfigureAwait(false); + } + private async Task SendRpcAsyncInternal(string cmd, object payload, Optional evt, RequestOptions options) where TResponse : class { - if (!ignoreState) + if (!options.IgnoreState) CheckState(); byte[] bytes = null; @@ -233,7 +230,7 @@ namespace Discord.API var requestTracker = new RpcRequest(options); _requests[guid] = requestTracker; - await _requestQueue.SendAsync(new WebSocketRequest(_webSocketClient, bytes, true, options), group, bucketId, guildId).ConfigureAwait(false); + await _requestQueue.SendAsync(new WebSocketRequest(_webSocketClient, bytes, true, options)).ConfigureAwait(false); await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); return await requestTracker.Promise.Task.ConfigureAwait(false); } @@ -241,33 +238,37 @@ namespace Discord.API //Rpc public async Task SendAuthenticateAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new AuthenticateParams { AccessToken = _authToken }; - return await SendRpcAsync("AUTHENTICATE", msg, ignoreState: true, options: options).ConfigureAwait(false); + options.IgnoreState = true; + return await SendRpcAsync("AUTHENTICATE", msg, options: options).ConfigureAwait(false); } public async Task SendAuthorizeAsync(string[] scopes, string rpcToken = null, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new AuthorizeParams { ClientId = _clientId, Scopes = scopes, RpcToken = rpcToken != null ? rpcToken : Optional.Create() }; - if (options == null) - options = new RequestOptions(); if (options.Timeout == null) options.Timeout = 60000; //This requires manual input on the user's end, lets give them more time - return await SendRpcAsync("AUTHORIZE", msg, ignoreState: true, options: options).ConfigureAwait(false); + options.IgnoreState = true; + return await SendRpcAsync("AUTHORIZE", msg, options: options).ConfigureAwait(false); } public async Task SendGetGuildsAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); return await SendRpcAsync("GET_GUILDS", null, options: options).ConfigureAwait(false); } public async Task SendGetGuildAsync(ulong guildId, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new GetGuildParams { GuildId = guildId @@ -276,6 +277,7 @@ namespace Discord.API } public async Task SendGetChannelsAsync(ulong guildId, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new GetChannelsParams { GuildId = guildId @@ -284,6 +286,7 @@ namespace Discord.API } public async Task SendGetChannelAsync(ulong channelId, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new GetChannelParams { ChannelId = channelId @@ -293,6 +296,7 @@ namespace Discord.API public async Task SendSetLocalVolumeAsync(int volume, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new SetLocalVolumeParams { Volume = volume @@ -301,6 +305,7 @@ namespace Discord.API } public async Task SendSelectVoiceChannelAsync(ulong channelId, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new SelectVoiceChannelParams { ChannelId = channelId @@ -310,6 +315,7 @@ namespace Discord.API public async Task SendChannelSubscribeAsync(string evt, ulong channelId, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new ChannelSubscriptionParams { ChannelId = channelId @@ -318,6 +324,7 @@ namespace Discord.API } public async Task SendChannelUnsubscribeAsync(string evt, ulong channelId, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new ChannelSubscriptionParams { ChannelId = channelId @@ -327,6 +334,7 @@ namespace Discord.API public async Task SendGuildSubscribeAsync(string evt, ulong guildId, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new GuildSubscriptionParams { GuildId = guildId @@ -335,6 +343,7 @@ namespace Discord.API } public async Task SendGuildUnsubscribeAsync(string evt, ulong guildId, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new GuildSubscriptionParams { GuildId = guildId diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 8cbda4155..263d20b44 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace Discord.Rpc { - public partial class DiscordRpcClient : DiscordClient + public partial class DiscordRpcClient : BaseDiscordClient { private readonly Logger _rpcLogger; private readonly JsonSerializer _serializer; diff --git a/src/Discord.Net.Rpc/Entities/RpcGuild.cs b/src/Discord.Net.Rpc/Entities/RpcGuild.cs index c42afb975..4fbde60a9 100644 --- a/src/Discord.Net.Rpc/Entities/RpcGuild.cs +++ b/src/Discord.Net.Rpc/Entities/RpcGuild.cs @@ -22,7 +22,5 @@ namespace Discord.Rpc { Name = model.Name; } - - bool IEntity.IsAttached => false; }*/ } diff --git a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs index 39c929ab6..cccda4290 100644 --- a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs @@ -156,8 +156,9 @@ namespace Discord.API } //Core - private async Task SendGatewayInternalAsync(GatewayOpCode opCode, object payload, - BucketGroup group, int bucketId, ulong guildId, RequestOptions options) + 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(); @@ -166,25 +167,19 @@ namespace Discord.API payload = new WebSocketMessage { Operation = (int)opCode, Payload = payload }; if (payload != null) bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); - await RequestQueue.SendAsync(new WebSocketRequest(_gatewayClient, bytes, true, options), group, bucketId, guildId).ConfigureAwait(false); + await RequestQueue.SendAsync(new WebSocketRequest(_gatewayClient, bytes, true, options)).ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); } //Gateway - public Task SendGatewayAsync(GatewayOpCode opCode, object payload, - GlobalBucket bucket = GlobalBucket.GeneralGateway, RequestOptions options = null) - => SendGatewayInternalAsync(opCode, payload, BucketGroup.Global, (int)bucket, 0, options); - - public Task SendGatewayAsync(GatewayOpCode opCode, object payload, - GuildBucket bucket, ulong guildId, RequestOptions options = null) - => SendGatewayInternalAsync(opCode, payload, BucketGroup.Guild, (int)bucket, guildId, options); - public async Task GetGatewayAsync(RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); return await SendAsync("GET", "gateway", options: options).ConfigureAwait(false); } public async Task SendIdentifyAsync(int largeThreshold = 100, bool useCompression = true, int shardID = 0, int totalShards = 1, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var props = new Dictionary { ["$device"] = "Discord.Net" @@ -203,6 +198,7 @@ namespace Discord.API } public async Task SendResumeAsync(string sessionId, int lastSeq, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var msg = new ResumeParams() { Token = _authToken, @@ -213,10 +209,12 @@ namespace Discord.API } public async Task SendHeartbeatAsync(int lastSeq, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false); } public async Task SendStatusUpdateAsync(long? idleSince, Game game, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var args = new StatusUpdateParams { IdleSince = idleSince, @@ -226,10 +224,12 @@ namespace Discord.API } 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); } public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); var payload = new VoiceStateUpdateParams { GuildId = guildId, @@ -241,6 +241,7 @@ namespace Discord.API } public async Task SendGuildSyncAsync(IEnumerable guildIds, RequestOptions options = null) { + options = RequestOptions.CreateOrClone(options); await SendGatewayAsync(GatewayOpCode.GuildSync, guildIds, options: options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.WebSocket/DataStore.cs b/src/Discord.Net.WebSocket/ClientState.cs similarity index 98% rename from src/Discord.Net.WebSocket/DataStore.cs rename to src/Discord.Net.WebSocket/ClientState.cs index d2b93c671..11d952842 100644 --- a/src/Discord.Net.WebSocket/DataStore.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -5,7 +5,7 @@ using System.Linq; namespace Discord.WebSocket { - internal class DataStore + internal class ClientState { private const int CollectionConcurrencyLevel = 1; //WebSocket updater/event handler. //TODO: Needs profiling, increase to 2? private const double AverageChannelsPerGuild = 10.22; //Source: Googie2149 @@ -29,7 +29,7 @@ namespace Discord.WebSocket _groupChannels.Select(x => GetChannel(x) as IPrivateChannel)) .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); - public DataStore(int guildCount, int dmChannelCount) + public ClientState(int guildCount, int dmChannelCount) { double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount; double estimatedUsersCount = guildCount * AverageUsersPerGuild; diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 6f93f7fa6..c8f1a4535 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -19,7 +19,7 @@ using System.Threading.Tasks; namespace Discord.WebSocket { - public partial class DiscordSocketClient : DiscordClient, IDiscordClient + public partial class DiscordSocketClient : BaseDiscordClient, IDiscordClient { private readonly ConcurrentQueue _largeGuilds; private readonly Logger _gatewayLogger; @@ -49,14 +49,14 @@ namespace Discord.WebSocket internal int MessageCacheSize { get; private set; } internal int LargeThreshold { get; private set; } internal AudioMode AudioMode { get; private set; } - internal DataStore DataStore { get; private set; } + internal ClientState State { get; private set; } internal int ConnectionTimeout { get; private set; } internal WebSocketProvider WebSocketProvider { get; private set; } public new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; public new SocketSelfUser CurrentUser => base.CurrentUser as SocketSelfUser; - public IReadOnlyCollection PrivateChannels => DataStore.PrivateChannels; - internal IReadOnlyCollection Guilds => DataStore.Guilds; + public IReadOnlyCollection PrivateChannels => State.PrivateChannels; + internal IReadOnlyCollection Guilds => State.Guilds; /// Creates a new REST/WebSocket discord client. public DiscordSocketClient() : this(new DiscordSocketConfig()) { } @@ -72,7 +72,7 @@ namespace Discord.WebSocket AudioMode = config.AudioMode; WebSocketProvider = config.WebSocketProvider; ConnectionTimeout = config.ConnectionTimeout; - DataStore = new DataStore(0, 0); + State = new ClientState(0, 0); _nextAudioId = 1; _gatewayLogger = LogManager.CreateLogger("Gateway"); @@ -111,7 +111,7 @@ namespace Discord.WebSocket protected override async Task OnLoginAsync(TokenType tokenType, string token) { - var voiceRegions = await ApiClient.GetVoiceRegionsAsync().ConfigureAwait(false); + var voiceRegions = await ApiClient.GetVoiceRegionsAsync(new RequestOptions { IgnoreState = true}).ConfigureAwait(false); _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id); } protected override async Task OnLogoutAsync() @@ -242,7 +242,7 @@ namespace Discord.WebSocket while (_largeGuilds.TryDequeue(out guildId)) { } //Raise virtual GUILD_UNAVAILABLEs - foreach (var guild in DataStore.Guilds) + foreach (var guild in State.Guilds) { if (guild._available) await _guildUnavailableEvent.InvokeAsync(guild).ConfigureAwait(false); @@ -322,7 +322,7 @@ namespace Discord.WebSocket /// public SocketGuild GetGuild(ulong id) { - return DataStore.GetGuild(id); + return State.GetGuild(id); } /// public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) @@ -331,7 +331,7 @@ namespace Discord.WebSocket /// public IChannel GetChannel(ulong id) { - return DataStore.GetChannel(id); + return State.GetChannel(id); } /// @@ -345,12 +345,12 @@ namespace Discord.WebSocket /// public IUser GetUser(ulong id) { - return DataStore.GetUser(id); + return State.GetUser(id); } /// public IUser GetUser(string username, string discriminator) { - return DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); + return State.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); } /// @@ -486,7 +486,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var dataStore = new DataStore(data.Guilds.Length, data.PrivateChannels.Length); + var dataStore = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); var currentUser = SocketSelfUser.Create(this, data.User); int unavailableGuilds = 0; @@ -505,7 +505,7 @@ namespace Discord.WebSocket _sessionId = data.SessionId; base.CurrentUser = currentUser; _unavailableGuilds = unavailableGuilds; - DataStore = dataStore; + State = dataStore; } catch (Exception ex) { diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index e67899de9..ccbbd4850 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -15,6 +15,7 @@ namespace Discord.WebSocket internal SocketSelfUser(DiscordSocketClient discord, ulong id) : base(discord, id) { + Status = UserStatus.Online; } internal new static SocketSelfUser Create(DiscordSocketClient discord, Model model) { diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 87691f427..5ab68d556 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -15,7 +15,7 @@ namespace Discord.WebSocket public string Discriminator => DiscriminatorValue.ToString("D4"); public string Mention => MentionUtils.MentionUser(Id); public virtual Game? Game => null; - public virtual UserStatus Status => UserStatus.Unknown; + public virtual UserStatus Status { get; internal set; } internal SocketUser(DiscordSocketClient discord, ulong id) : base(discord, id) From 4678544fed606df2c427f523577b0b91159b7c04 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 07:32:26 -0300 Subject: [PATCH 005/102] Added remaining gateway events, added IAudioChannel, added CacheModes --- src/Discord.Net.Commands/project.json | 4 +- src/Discord.Net.Core/Entities/CacheMode.cs | 8 + .../Entities/Channels/IAudioChannel.cs | 6 + .../Entities/Channels/IChannel.cs | 7 +- .../Entities/Channels/IGroupChannel.cs | 10 +- .../Entities/Channels/IGuildChannel.cs | 11 +- .../Entities/Channels/IMessageChannel.cs | 11 +- .../Entities/Channels/IVoiceChannel.cs | 2 +- src/Discord.Net.Core/Entities/Guilds/IBan.cs | 7 +- .../Entities/Guilds/IGuild.cs | 14 +- src/Discord.Net.Core/Entities/Users/IUser.cs | 4 +- .../Extensions/CollectionExtensions.cs | 0 .../TaskCompletionSourceExtensions.cs | 8 + src/Discord.Net.Core/RequestOptions.cs | 2 +- .../Utils/ConcurrentHashSet.cs | 5 + .../Utils/Extensions/Permissions.cs | 152 ------ src/Discord.Net.Core/Utils/MentionsHelper.cs | 5 +- .../Entities/Channels/IRestAudioChannel.cs | 6 + .../Entities/Channels/IRestMessageChannel.cs | 25 + .../Entities/Channels/IRestPrivateChannel.cs | 9 + .../Entities/Channels/RestChannel.cs | 25 +- .../Entities/Channels/RestDMChannel.cs | 50 +- .../Entities/Channels/RestGroupChannel.cs | 48 +- .../Entities/Channels/RestGuildChannel.cs | 18 +- .../Entities/Channels/RestTextChannel.cs | 52 ++- .../Entities/Channels/RestVoiceChannel.cs | 6 +- .../Entities/Guilds/RestGuild.cs | 59 ++- .../Entities/Users/RestUser.cs | 12 +- .../Entities/Users/UserHelper.cs | 2 +- .../Entities/{ => Guilds}/RpcGuild.cs | 0 .../Audio/AudioClient.cs | 6 +- src/Discord.Net.WebSocket/ClientState.cs | 6 +- .../DiscordSocketClient.Events.cs | 104 ++--- .../DiscordSocketClient.cs | 379 ++++++++------- .../Entities/Channels/ISocketAudioChannel.cs | 6 + .../Channels/ISocketMessageChannel.cs | 32 ++ .../Channels/ISocketPrivateChannel.cs | 9 + .../Entities/Channels/SocketChannel.cs | 32 +- .../Entities/Channels/SocketChannelHelper.cs | 81 ++++ .../Entities/Channels/SocketDMChannel.cs | 119 ++--- .../Entities/Channels/SocketGroupChannel.cs | 161 +++++-- .../Entities/Channels/SocketGuildChannel.cs | 61 +-- .../Entities/Channels/SocketTextChannel.cs | 118 +++-- .../Entities/Channels/SocketVoiceChannel.cs | 38 +- .../Entities/Guilds/SocketGuild.cs | 434 ++++++++++++++++-- .../Entities/Messages/MessageCache.cs | 7 +- .../Entities/Messages/SocketMessage.cs | 19 +- .../Entities/Messages/SocketSystemMessage.cs | 15 +- .../Entities/Messages/SocketUserMessage.cs | 14 +- .../Entities/Roles/SocketRole.cs | 58 +++ .../Entities/Users/SocketGlobalUser.cs | 50 +- .../Entities/Users/SocketGroupUser.cs | 26 +- .../Entities/Users/SocketGuildUser.cs | 69 ++- .../Entities/Users/SocketPresence.cs | 6 +- .../Entities/Users/SocketSelfUser.cs | 30 +- .../Entities/Users/SocketSimpleUser.cs | 34 ++ .../Entities/Users/SocketUser.cs | 44 +- .../Entities/Users/SocketVoiceState.cs | 2 +- 58 files changed, 1679 insertions(+), 849 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/CacheMode.cs create mode 100644 src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs rename src/Discord.Net.Core/{Utils => }/Extensions/CollectionExtensions.cs (100%) rename src/Discord.Net.Core/{Utils => }/Extensions/TaskCompletionSourceExtensions.cs (58%) delete mode 100644 src/Discord.Net.Core/Utils/Extensions/Permissions.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/IRestAudioChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs rename src/Discord.Net.Rpc/Entities/{ => Guilds}/RpcGuild.cs (100%) create mode 100644 src/Discord.Net.WebSocket/Entities/Channels/ISocketAudioChannel.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Channels/ISocketPrivateChannel.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index 6505ea3c8..c0449bf35 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -30,7 +30,9 @@ }, "dependencies": { - "Discord.Net": "1.0.0-*" + "Discord.Net.Core": { + "target": "project" + } }, "frameworks": { diff --git a/src/Discord.Net.Core/Entities/CacheMode.cs b/src/Discord.Net.Core/Entities/CacheMode.cs new file mode 100644 index 000000000..a047bd616 --- /dev/null +++ b/src/Discord.Net.Core/Entities/CacheMode.cs @@ -0,0 +1,8 @@ +namespace Discord +{ + public enum CacheMode + { + AllowDownload, + CacheOnly + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs new file mode 100644 index 000000000..9b8074efb --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs @@ -0,0 +1,6 @@ +namespace Discord +{ + public interface IAudioChannel + { + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs index e3990204c..c81911a45 100644 --- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -5,13 +5,10 @@ namespace Discord { public interface IChannel : ISnowflakeEntity { - IReadOnlyCollection CachedUsers { get; } - /// Gets a collection of all users in this channel. - IAsyncEnumerable> GetUsersAsync(); + IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload); /// Gets a user in this channel with the provided id. - Task GetUserAsync(ulong id); - IUser GetCachedUser(ulong id); + Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); } } diff --git a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs index dafc997a7..23f5b4784 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs @@ -1,15 +1,9 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Discord { - public interface IGroupChannel : IMessageChannel, IPrivateChannel + public interface IGroupChannel : IMessageChannel, IPrivateChannel, IAudioChannel { - ///// Adds a user to this group. - //Task AddUserAsync(IUser user); - - //new IReadOnlyCollection CachedUsers { get; } - /// Leaves this group. Task LeaveAsync(); } diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index 0927c2b79..085771fbf 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -14,7 +14,8 @@ namespace Discord /// Gets the id of the guild this channel is a member of. ulong GuildId { get; } - new IReadOnlyCollection CachedUsers { 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. @@ -23,9 +24,6 @@ namespace Discord Task CreateInviteAsync(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false); /// Returns a collection of all invites to this channel. Task> GetInvitesAsync(); - - /// Gets a collection of permission overwrites for this channel. - IReadOnlyCollection PermissionOverwrites { get; } /// Modifies this guild channel. Task ModifyAsync(Action func); @@ -44,9 +42,8 @@ namespace Discord Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions); /// Gets a collection of all users in this channel. - new IAsyncEnumerable> GetUsersAsync(); + new IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload); /// Gets a user in this channel with the provided id. - new Task GetUserAsync(ulong id); - new IGuildUser GetCachedUser(ulong id); + new Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); } } \ No newline at end of file diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index e8089d2af..b8333c64a 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -7,9 +7,6 @@ namespace Discord { public interface IMessageChannel : IChannel { - /// Gets all messages in this channel's cache. - IReadOnlyCollection CachedMessages { get; } - /// Sends a message to this message channel. Task SendMessageAsync(string text, bool isTTS = false); /// Sends a file to this text channel, with an optional caption. @@ -18,13 +15,11 @@ namespace Discord Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); /// Gets a message from this message channel with the given id, or null if not found. - Task GetMessageAsync(ulong id); - /// Gets the message from this channel's cache with the given id, or null if not found. - IMessage GetCachedMessage(ulong id); + Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); /// 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); /// 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); /// Gets a collection of pinned messages in this channel. Task> GetPinnedMessagesAsync(); /// Bulk deletes multiple messages. diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs index 5f6e8c817..75f6a0190 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Discord { - public interface IVoiceChannel : IGuildChannel + public interface IVoiceChannel : IGuildChannel, IAudioChannel { /// Gets the bitrate, in bits per second, clients in this voice channel are requested to use. int Bitrate { get; } diff --git a/src/Discord.Net.Core/Entities/Guilds/IBan.cs b/src/Discord.Net.Core/Entities/Guilds/IBan.cs index aee2cc198..05ab0c00f 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IBan.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IBan.cs @@ -1,10 +1,5 @@ -//using Discord.Rest; -using System.Diagnostics; -using Model = Discord.API.Ban; - -namespace Discord +namespace Discord { - public interface IBan { IUser User { get; } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 489f9e8f5..414a7cc74 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -41,7 +41,6 @@ namespace Discord 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. @@ -52,7 +51,6 @@ namespace Discord IReadOnlyCollection Features { get; } /// Gets a collection of all roles in this guild. IReadOnlyCollection Roles { get; } - IReadOnlyCollection CachedUsers { get; } /// Modifies this guild. Task ModifyAsync(Action func); @@ -77,10 +75,9 @@ namespace Discord Task RemoveBanAsync(ulong userId); /// Gets a collection of all channels in this guild. - Task> GetChannelsAsync(); + Task> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload); /// Gets the channel in this guild with the provided id, or null if not found. - Task GetChannelAsync(ulong id); - IGuildChannel GetCachedChannel(ulong id); + Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); /// Creates a new text channel. Task CreateTextChannelAsync(string name); /// Creates a new voice channel. @@ -98,12 +95,11 @@ namespace Discord Task CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false); /// Gets a collection of all users in this guild. - Task> GetUsersAsync(); + Task> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload); //TODO: shouldnt this be paged? /// Gets the user in this guild with the provided id, or null if not found. - Task GetUserAsync(ulong id); - IGuildUser GetCachedUser(ulong id); + Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); /// Gets the current user for this guild. - Task GetCurrentUserAsync(); + Task GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload); /// 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. diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index 30aa974cf..43f441e7e 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -17,8 +17,8 @@ namespace Discord /// Gets the username for this user. string Username { get; } - /// Returns a private message channel to this user, returning null if one does not exist or is not cached. - IDMChannel GetCachedDMChannel(); + /// Returns a private message channel to this user, creating one if it does not already exist. + Task GetDMChannelAsync(CacheMode mode = CacheMode.AllowDownload); /// Returns a private message channel to this user, creating one if it does not already exist. Task CreateDMChannelAsync(); } diff --git a/src/Discord.Net.Core/Utils/Extensions/CollectionExtensions.cs b/src/Discord.Net.Core/Extensions/CollectionExtensions.cs similarity index 100% rename from src/Discord.Net.Core/Utils/Extensions/CollectionExtensions.cs rename to src/Discord.Net.Core/Extensions/CollectionExtensions.cs diff --git a/src/Discord.Net.Core/Utils/Extensions/TaskCompletionSourceExtensions.cs b/src/Discord.Net.Core/Extensions/TaskCompletionSourceExtensions.cs similarity index 58% rename from src/Discord.Net.Core/Utils/Extensions/TaskCompletionSourceExtensions.cs rename to src/Discord.Net.Core/Extensions/TaskCompletionSourceExtensions.cs index be7c42e9c..a5a715b4c 100644 --- a/src/Discord.Net.Core/Utils/Extensions/TaskCompletionSourceExtensions.cs +++ b/src/Discord.Net.Core/Extensions/TaskCompletionSourceExtensions.cs @@ -5,10 +5,18 @@ namespace Discord { internal static class TaskCompletionSourceExtensions { + 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/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 452b75444..9c9986b29 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -11,7 +11,7 @@ internal bool IgnoreState { get; set; } - public static RequestOptions CreateOrClone(RequestOptions options) + internal static RequestOptions CreateOrClone(RequestOptions options) { if (options == null) return new RequestOptions(); diff --git a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs index 1805649a9..846ad6348 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -9,6 +9,11 @@ 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 + public static class ConcurrentHashSet + { + public static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount; + } + [DebuggerDisplay("Count = {Count}")] internal class ConcurrentHashSet : IReadOnlyCollection { diff --git a/src/Discord.Net.Core/Utils/Extensions/Permissions.cs b/src/Discord.Net.Core/Utils/Extensions/Permissions.cs deleted file mode 100644 index b8593a31d..000000000 --- a/src/Discord.Net.Core/Utils/Extensions/Permissions.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Discord.Extensions -{ - internal static class Permissions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission bit) - => GetValue(allow, deny, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(ulong allow, ulong deny, GuildPermission bit) - => GetValue(allow, deny, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(ulong allow, ulong deny, byte bit) - { - if (HasBit(allow, bit)) - return PermValue.Allow; - else if (HasBit(deny, bit)) - return PermValue.Deny; - else - return PermValue.Inherit; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(ulong value, ChannelPermission bit) - => GetValue(value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(ulong value, GuildPermission bit) - => GetValue(value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(ulong value, byte bit) => HasBit(value, bit); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission bit) - => SetValue(ref rawValue, value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong rawValue, bool? value, GuildPermission bit) - => SetValue(ref rawValue, value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong rawValue, bool? value, byte bit) - { - if (value.HasValue) - { - if (value == true) - SetBit(ref rawValue, bit); - else - UnsetBit(ref rawValue, bit); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission bit) - => SetValue(ref allow, ref deny, value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission bit) - => SetValue(ref allow, ref deny, value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, byte bit) - { - if (value.HasValue) - { - switch (value) - { - case PermValue.Allow: - SetBit(ref allow, bit); - UnsetBit(ref deny, bit); - break; - case PermValue.Deny: - UnsetBit(ref allow, bit); - SetBit(ref deny, bit); - break; - default: - UnsetBit(ref allow, bit); - UnsetBit(ref deny, bit); - break; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool HasBit(ulong value, byte bit) => (value & (1U << bit)) != 0; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetBit(ref ulong value, byte bit) => value |= (1U << bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); - - public static ulong ResolveGuild(IGuild guild, IGuildUser user) - { - ulong resolvedPermissions = 0; - - if (user.Id == guild.OwnerId) - resolvedPermissions = GuildPermissions.All.RawValue; //Owners always have all permissions - else - { - foreach (var role in user.RoleIds) - resolvedPermissions |= guild.GetRole(role).Permissions.RawValue; - if (GetValue(resolvedPermissions, GuildPermission.Administrator)) - resolvedPermissions = GuildPermissions.All.RawValue; //Administrators always have all permissions - } - return resolvedPermissions; - } - - /*public static ulong ResolveChannel(IGuildUser user, IGuildChannel channel) - { - return ResolveChannel(user, channel, ResolveGuild(user)); - }*/ - public static ulong ResolveChannel(IGuild guild, IGuildChannel channel, IGuildUser user, ulong guildPermissions) - { - ulong resolvedPermissions = 0; - - ulong mask = ChannelPermissions.All(channel).RawValue; - if (/*user.Id == user.Guild.OwnerId || */GetValue(guildPermissions, GuildPermission.Administrator)) - resolvedPermissions = mask; //Owners and administrators always have all permissions - else - { - //Start with this user's guild permissions - resolvedPermissions = guildPermissions; - - OverwritePermissions? perms; - var roleIds = user.RoleIds; - if (roleIds.Count > 0) - { - ulong deniedPermissions = 0UL, allowedPermissions = 0UL; - foreach (var roleId in roleIds) - { - perms = channel.GetPermissionOverwrite(guild.GetRole(roleId)); - if (perms != null) - { - deniedPermissions |= perms.Value.DenyValue; - allowedPermissions |= perms.Value.AllowValue; - } - } - resolvedPermissions = (resolvedPermissions & ~deniedPermissions) | allowedPermissions; - } - perms = channel.GetPermissionOverwrite(user); - if (perms != null) - resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; - - //TODO: C#7 Typeswitch candidate - var textChannel = channel as ITextChannel; - var voiceChannel = channel as IVoiceChannel; - if (textChannel != null && !GetValue(resolvedPermissions, ChannelPermission.ReadMessages)) - resolvedPermissions = 0; //No read permission on a text channel removes all other permissions - else if (voiceChannel != null && !GetValue(resolvedPermissions, ChannelPermission.Connect)) - resolvedPermissions = 0; //No connect permission on a voice channel removes all other permissions - 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/MentionsHelper.cs b/src/Discord.Net.Core/Utils/MentionsHelper.cs index 58c0fe16d..19c0fd511 100644 --- a/src/Discord.Net.Core/Utils/MentionsHelper.cs +++ b/src/Discord.Net.Core/Utils/MentionsHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace Discord { @@ -34,7 +35,7 @@ namespace Discord { if (userMention.Id == id) { - user = channel?.GetCachedUser(id) as TUser; + user = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult() as TUser; if (user == null) //User not found, fallback to basic mention info user = userMention; break; @@ -136,7 +137,7 @@ namespace Discord return ""; case ChannelMentionHandling.Name: IGuildChannel channel = null; - channel = guild.GetCachedChannel(id); + channel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); if (channel != null) return $"#{channel.Name}"; else diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestAudioChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestAudioChannel.cs new file mode 100644 index 000000000..2c3341537 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/IRestAudioChannel.cs @@ -0,0 +1,6 @@ +namespace Discord.Rest +{ + public interface IRestAudioChannel : IAudioChannel + { + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs new file mode 100644 index 000000000..e15311f02 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + public interface IRestMessageChannel : IMessageChannel + { + /// Sends a message to this message channel. + new Task SendMessageAsync(string text, bool isTTS = false); + /// Sends a file to this text channel, with an optional caption. + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false); + /// Sends a file to this text channel, with an optional caption. + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); + + /// Gets a message from this message channel with the given id, or null if not found. + Task GetMessageAsync(ulong id); + /// Gets the last N messages from this message channel. + IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of messages in this channel. + IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of pinned messages in this channel. + new Task> GetPinnedMessagesAsync(); + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs new file mode 100644 index 000000000..a6939f81e --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Discord.Rest +{ + public interface IRestPrivateChannel : IPrivateChannel + { + new IReadOnlyCollection Recipients { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 868bc7e37..bd2584dce 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -23,6 +23,17 @@ namespace Discord.Rest return RestTextChannel.Create(discord, model); case ChannelType.Voice: return RestVoiceChannel.Create(discord, model); + case ChannelType.DM: + case ChannelType.Group: + return CreatePrivate(discord, model) as RestChannel; + default: + throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); + } + } + internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model) + { + switch (model.Type) + { case ChannelType.DM: return RestDMChannel.Create(discord, model); case ChannelType.Group: @@ -35,14 +46,10 @@ namespace Discord.Rest public abstract Task UpdateAsync(); - //IChannel - IReadOnlyCollection IChannel.CachedUsers => ImmutableArray.Create(); - - IUser IChannel.GetCachedUser(ulong id) - => null; - Task IChannel.GetUserAsync(ulong id) - => Task.FromResult(null); - IAsyncEnumerable> IChannel.GetUsersAsync() - => ImmutableArray.Create>().ToAsyncEnumerable(); + //IChannel + Task IChannel.GetUserAsync(ulong id, CacheMode mode) + => Task.FromResult(null); //Overriden + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 6e7451f22..3f399da57 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestDMChannel : RestChannel, IDMChannel, IUpdateable + public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel, IUpdateable { public RestUser CurrentUser { get; private set; } public RestUser Recipient { get; private set; } @@ -75,23 +75,39 @@ namespace Discord.Rest public override string ToString() => $"@{Recipient}"; private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; - + + //IDMChannel IUser IDMChannel.Recipient => Recipient; + //IRestPrivateChannel + IReadOnlyCollection IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + //IPrivateChannel IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); //IMessageChannel - IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); - IMessage IMessageChannel.GetCachedMessage(ulong id) => null; - - async Task IMessageChannel.GetMessageAsync(ulong id) - => await GetMessageAsync(id); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) - => GetMessagesAsync(limit); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - => GetMessagesAsync(fromMessageId, dir, limit); + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id); + else + return null; + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(limit); + else + return ImmutableArray.Create>().ToAsyncEnumerable(); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(fromMessageId, dir, limit); + else + return ImmutableArray.Create>().ToAsyncEnumerable(); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) @@ -103,14 +119,10 @@ namespace Discord.Rest IDisposable IMessageChannel.EnterTypingState() => EnterTypingState(); - //IChannel - IReadOnlyCollection IChannel.CachedUsers => Users; - - IUser IChannel.GetCachedUser(ulong id) - => GetUser(id); - Task IChannel.GetUserAsync(ulong id) + //IChannel + Task IChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync() - => ImmutableArray.Create>().ToAsyncEnumerable(); + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 0324ba1fe..7e0806270 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -10,11 +10,11 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGroupChannel : RestChannel, IGroupChannel, IUpdateable + 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(); @@ -86,20 +86,34 @@ namespace Discord.Rest public IDisposable EnterTypingState() => ChannelHelper.EnterTypingState(this, Discord); + //ISocketPrivateChannel + IReadOnlyCollection IRestPrivateChannel.Recipients => Recipients; + //IPrivateChannel IReadOnlyCollection IPrivateChannel.Recipients => Recipients; //IMessageChannel - IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); - - IMessage IMessageChannel.GetCachedMessage(ulong id) - => null; - async Task IMessageChannel.GetMessageAsync(ulong id) - => await GetMessageAsync(id); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) - => GetMessagesAsync(limit); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - => GetMessagesAsync(fromMessageId, dir, limit); + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id); + else + return null; + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(limit); + else + return ImmutableArray.Create>().ToAsyncEnumerable(); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(fromMessageId, dir, limit); + else + return ImmutableArray.Create>().ToAsyncEnumerable(); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); @@ -112,14 +126,10 @@ namespace Discord.Rest IDisposable IMessageChannel.EnterTypingState() => EnterTypingState(); - //IChannel - IReadOnlyCollection IChannel.CachedUsers => Users; - - IUser IChannel.GetCachedUser(ulong id) - => GetUser(id); - Task IChannel.GetUserAsync(ulong id) + //IChannel + Task IChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync() + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index d6a4143d3..7d7547713 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -135,24 +135,16 @@ namespace Discord.Rest => await RemovePermissionOverwriteAsync(role); async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) => await RemovePermissionOverwriteAsync(user); - - IReadOnlyCollection IGuildChannel.CachedUsers - => ImmutableArray.Create(); - IAsyncEnumerable> IGuildChannel.GetUsersAsync() + + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? - Task IGuildChannel.GetUserAsync(ulong id) + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? - IGuildUser IGuildChannel.GetCachedUser(ulong id) - => null; //IChannel - IReadOnlyCollection IChannel.CachedUsers - => ImmutableArray.Create(); - IUser IChannel.GetCachedUser(ulong id) - => null; - IAsyncEnumerable> IChannel.GetUsersAsync() + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? - Task IChannel.GetUserAsync(ulong id) + Task IChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 6e15825e0..3e660b53b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; +using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestTextChannel : RestGuildChannel, ITextChannel + public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { public string Topic { get; private set; } @@ -67,22 +68,43 @@ namespace Discord.Rest => ChannelHelper.EnterTypingState(this, Discord); //IGuildChannel - async Task IGuildChannel.GetUserAsync(ulong id) - => await GetUserAsync(id); - IAsyncEnumerable> IGuildChannel.GetUsersAsync() - => GetUsersAsync(); + async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetUserAsync(id); + else + return null; + } + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetUsersAsync(); + else + return ImmutableArray.Create>().ToAsyncEnumerable(); + } //IMessageChannel - IReadOnlyCollection IMessageChannel.CachedMessages - => ImmutableArray.Create(); - IMessage IMessageChannel.GetCachedMessage(ulong id) - => null; - async Task IMessageChannel.GetMessageAsync(ulong id) - => await GetMessageAsync(id); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) - => GetMessagesAsync(limit); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - => GetMessagesAsync(fromMessageId, dir, limit); + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id); + else + return null; + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(limit); + else + return ImmutableArray.Create>().ToAsyncEnumerable(); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(fromMessageId, dir, limit); + else + return ImmutableArray.Create>().ToAsyncEnumerable(); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 4653ee381..1ff7c3075 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -11,7 +11,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestVoiceChannel : RestGuildChannel, IVoiceChannel + public class RestVoiceChannel : RestGuildChannel, IVoiceChannel, IRestAudioChannel { public int Bitrate { get; private set; } public int UserLimit { get; private set; } @@ -41,9 +41,9 @@ namespace Discord.Rest Task IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } //IGuildChannel - Task IGuildChannel.GetUserAsync(ulong id) + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); - IAsyncEnumerable> IGuildChannel.GetUsersAsync() + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) => ImmutableArray.Create>().ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 601329b5c..ae61187ab 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Discord.API.Rest; using Model = Discord.API.Guild; +using System.Linq; namespace Discord.Rest { @@ -149,7 +150,7 @@ namespace Discord.Rest return null; } - public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) + public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) { var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); _roles = _roles.Add(role.Id, role); @@ -157,8 +158,8 @@ namespace Discord.Rest } //Users - public Task> GetUsersAsync() - => GuildHelper.GetUsersAsync(this, Discord); + public IAsyncEnumerable> GetUsersAsync() + => GuildHelper.GetUsersAsync(this, Discord).ToAsyncEnumerable(); public Task GetUserAsync(ulong id) => GuildHelper.GetUserAsync(this, Discord, id); public Task GetCurrentUserAsync() @@ -170,19 +171,26 @@ namespace Discord.Rest //IGuild bool IGuild.Available => true; IAudioClient IGuild.AudioClient => null; - IReadOnlyCollection IGuild.CachedUsers => ImmutableArray.Create(); IRole IGuild.EveryoneRole => EveryoneRole; IReadOnlyCollection IGuild.Roles => Roles; async Task> IGuild.GetBansAsync() => await GetBansAsync(); - async Task> IGuild.GetChannelsAsync() - => await GetChannelsAsync(); - async Task IGuild.GetChannelAsync(ulong id) - => await GetChannelAsync(id); - IGuildChannel IGuild.GetCachedChannel(ulong id) - => null; + async Task> IGuild.GetChannelsAsync(CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetChannelsAsync(); + else + return ImmutableArray.Create(); + } + async Task IGuild.GetChannelAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetChannelAsync(id); + else + return null; + } async Task IGuild.CreateTextChannelAsync(string name) => await CreateTextChannelAsync(name); async Task IGuild.CreateVoiceChannelAsync(string name) @@ -198,15 +206,30 @@ namespace Discord.Rest IRole IGuild.GetRole(ulong id) => GetRole(id); + async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted) + => await CreateRoleAsync(name, permissions, color, isHoisted); - async Task> IGuild.GetUsersAsync() - => await GetUsersAsync(); - async Task IGuild.GetUserAsync(ulong id) - => await GetUserAsync(id); - IGuildUser IGuild.GetCachedUser(ulong id) - => null; - async Task IGuild.GetCurrentUserAsync() - => await GetCurrentUserAsync(); + async Task IGuild.GetUserAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetUserAsync(id); + else + return null; + } + async Task IGuild.GetCurrentUserAsync(CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetCurrentUserAsync(); + else + return null; + } + async Task> IGuild.GetUsersAsync(CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return (await GetUsersAsync().Flatten()).ToImmutableArray(); + else + return ImmutableArray.Create(); + } Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 6a42de397..5d704e590 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; @@ -42,10 +43,13 @@ namespace Discord.Rest public virtual async Task UpdateAsync() => Update(await UserHelper.GetAsync(this, Discord)); - - public Task CreateDMChannelAsync() + + public Task CreateDMChannelAsync() => UserHelper.CreateDMChannelAsync(this, Discord); - IDMChannel IUser.GetCachedDMChannel() => null; + Task IUser.GetDMChannelAsync(CacheMode mode) + => Task.FromResult(null); + async Task IUser.CreateDMChannelAsync() + => await CreateDMChannelAsync(); } } diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index ddf8055d9..012627283 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -44,7 +44,7 @@ namespace Discord.Rest await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); } - public static async Task CreateDMChannelAsync(IUser user, BaseDiscordClient client) + public static async Task CreateDMChannelAsync(IUser user, BaseDiscordClient client) { var args = new CreateDMChannelParams(user.Id); return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args)); diff --git a/src/Discord.Net.Rpc/Entities/RpcGuild.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs similarity index 100% rename from src/Discord.Net.Rpc/Entities/RpcGuild.cs rename to src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 202c65da9..5bf686dc4 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace Discord.Audio { - internal class AudioClient : IAudioClient, IDisposable + public class AudioClient : IAudioClient, IDisposable { public event Func Connected { @@ -56,7 +56,7 @@ namespace Discord.Audio private DiscordSocketClient Discord => Guild.Discord; /// Creates a new REST/WebSocket discord client. - public AudioClient(SocketGuild guild, int id) + internal AudioClient(SocketGuild guild, int id) { Guild = guild; @@ -90,7 +90,7 @@ namespace Discord.Audio } /// - public async Task ConnectAsync(string url, ulong userId, string sessionId, string token) + internal async Task ConnectAsync(string url, ulong userId, string sessionId, string token) { await _connectionLock.WaitAsync().ConfigureAwait(false); try diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index 11d952842..13beb0ffb 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -24,9 +24,9 @@ namespace Discord.WebSocket internal IReadOnlyCollection Guilds => _guilds.ToReadOnlyCollection(); internal IReadOnlyCollection Users => _users.ToReadOnlyCollection(); - internal IReadOnlyCollection PrivateChannels => - _dmChannels.Select(x => x.Value as IPrivateChannel).Concat( - _groupChannels.Select(x => GetChannel(x) as IPrivateChannel)) + 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) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs index 04aadaa3f..3c9bf4fba 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs @@ -33,170 +33,170 @@ namespace Discord.WebSocket private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); //Channels - public event Func ChannelCreated + public event Func ChannelCreated { add { _channelCreatedEvent.Add(value); } remove { _channelCreatedEvent.Remove(value); } } - private readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); - public event Func ChannelDestroyed + private readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); + public event Func ChannelDestroyed { add { _channelDestroyedEvent.Add(value); } remove { _channelDestroyedEvent.Remove(value); } } - private readonly AsyncEvent> _channelDestroyedEvent = new AsyncEvent>(); - public event Func ChannelUpdated + private readonly AsyncEvent> _channelDestroyedEvent = new AsyncEvent>(); + public event Func ChannelUpdated { add { _channelUpdatedEvent.Add(value); } remove { _channelUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); //Messages - public event Func MessageReceived + public event Func MessageReceived { add { _messageReceivedEvent.Add(value); } remove { _messageReceivedEvent.Remove(value); } } - private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); - public event Func, Task> MessageDeleted + private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); + public event Func, Task> MessageDeleted { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } - private readonly AsyncEvent, Task>> _messageDeletedEvent = new AsyncEvent, Task>>(); - public event Func, IMessage, Task> MessageUpdated + private readonly AsyncEvent, Task>> _messageDeletedEvent = new AsyncEvent, Task>>(); + public event Func, SocketMessage, Task> MessageUpdated { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } } - private readonly AsyncEvent, IMessage, Task>> _messageUpdatedEvent = new AsyncEvent, IMessage, Task>>(); + private readonly AsyncEvent, SocketMessage, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, Task>>(); //Roles - public event Func RoleCreated + public event Func RoleCreated { add { _roleCreatedEvent.Add(value); } remove { _roleCreatedEvent.Remove(value); } } - private readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); - public event Func RoleDeleted + private readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); + public event Func RoleDeleted { add { _roleDeletedEvent.Add(value); } remove { _roleDeletedEvent.Remove(value); } } - private readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); - public event Func RoleUpdated + private readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); + public event Func RoleUpdated { add { _roleUpdatedEvent.Add(value); } remove { _roleUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _roleUpdatedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _roleUpdatedEvent = new AsyncEvent>(); //Guilds - public event Func JoinedGuild + public event Func JoinedGuild { add { _joinedGuildEvent.Add(value); } remove { _joinedGuildEvent.Remove(value); } } - private AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); - public event Func LeftGuild + private AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); + public event Func LeftGuild { add { _leftGuildEvent.Add(value); } remove { _leftGuildEvent.Remove(value); } } - private AsyncEvent> _leftGuildEvent = new AsyncEvent>(); - public event Func GuildAvailable + private AsyncEvent> _leftGuildEvent = new AsyncEvent>(); + public event Func GuildAvailable { add { _guildAvailableEvent.Add(value); } remove { _guildAvailableEvent.Remove(value); } } - private AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); - public event Func GuildUnavailable + private AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); + public event Func GuildUnavailable { add { _guildUnavailableEvent.Add(value); } remove { _guildUnavailableEvent.Remove(value); } } - private AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); - public event Func GuildMembersDownloaded + private AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); + public event Func GuildMembersDownloaded { add { _guildMembersDownloadedEvent.Add(value); } remove { _guildMembersDownloadedEvent.Remove(value); } } - private AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); - public event Func GuildUpdated + private AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); + public event Func GuildUpdated { add { _guildUpdatedEvent.Add(value); } remove { _guildUpdatedEvent.Remove(value); } } - private AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); + private AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); //Users - public event Func UserJoined + public event Func UserJoined { add { _userJoinedEvent.Add(value); } remove { _userJoinedEvent.Remove(value); } } - private readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); - public event Func UserLeft + private readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); + public event Func UserLeft { add { _userLeftEvent.Add(value); } remove { _userLeftEvent.Remove(value); } } - private readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); - public event Func UserBanned + private readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); + public event Func UserBanned { add { _userBannedEvent.Add(value); } remove { _userBannedEvent.Remove(value); } } - private readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); - public event Func UserUnbanned + private readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); + public event Func UserUnbanned { add { _userUnbannedEvent.Add(value); } remove { _userUnbannedEvent.Remove(value); } } - private readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); - public event Func UserUpdated + private readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); + public event Func UserUpdated { add { _userUpdatedEvent.Add(value); } remove { _userUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); - public event Func UserPresenceUpdated + private readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); + public event Func UserPresenceUpdated { add { _userPresenceUpdatedEvent.Add(value); } remove { _userPresenceUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _userPresenceUpdatedEvent = new AsyncEvent>(); - public event Func UserVoiceStateUpdated + private readonly AsyncEvent> _userPresenceUpdatedEvent = new AsyncEvent>(); + public event Func UserVoiceStateUpdated { add { _userVoiceStateUpdatedEvent.Add(value); } remove { _userVoiceStateUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _userVoiceStateUpdatedEvent = new AsyncEvent>(); - public event Func CurrentUserUpdated + private readonly AsyncEvent> _userVoiceStateUpdatedEvent = new AsyncEvent>(); + public event Func CurrentUserUpdated { add { _selfUpdatedEvent.Add(value); } remove { _selfUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); - public event Func UserIsTyping + private readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); + public event Func UserIsTyping { add { _userIsTypingEvent.Add(value); } remove { _userIsTypingEvent.Remove(value); } } - private readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); - public event Func RecipientAdded + private readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); + public event Func RecipientAdded { add { _recipientAddedEvent.Add(value); } remove { _recipientAddedEvent.Remove(value); } } - private readonly AsyncEvent> _recipientAddedEvent = new AsyncEvent>(); - public event Func RecipientRemoved + private readonly AsyncEvent> _recipientAddedEvent = new AsyncEvent>(); + public event Func RecipientRemoved { add { _recipientRemovedEvent.Add(value); } remove { _recipientRemovedEvent.Remove(value); } } - private readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>(); //TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected; } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index c8f1a4535..03831818a 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -54,8 +54,8 @@ namespace Discord.WebSocket internal WebSocketProvider WebSocketProvider { get; private set; } public new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - public new SocketSelfUser CurrentUser => base.CurrentUser as SocketSelfUser; - public IReadOnlyCollection PrivateChannels => State.PrivateChannels; + public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } } + public IReadOnlyCollection PrivateChannels => State.PrivateChannels; internal IReadOnlyCollection Guilds => State.Guilds; /// Creates a new REST/WebSocket discord client. @@ -167,14 +167,23 @@ namespace Discord.WebSocket connectTask.TrySetException(new TimeoutException()); }); + await _gatewayLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); await ApiClient.ConnectAsync().ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Raising Event").ConfigureAwait(false); await _connectedEvent.InvokeAsync().ConfigureAwait(false); if (_sessionId != null) + { + await _gatewayLogger.DebugAsync("Resuming").ConfigureAwait(false); await ApiClient.SendResumeAsync(_sessionId, _lastSeq).ConfigureAwait(false); + } else - await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards:TotalShards).ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); + await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards).ConfigureAwait(false); + } + await _gatewayLogger.DebugAsync("Raising Event").ConfigureAwait(false); await _connectTask.Task.ConfigureAwait(false); if (!isReconnecting) _canReconnect = true; @@ -216,32 +225,33 @@ namespace Discord.WebSocket ConnectionState = ConnectionState.Disconnecting; await _gatewayLogger.InfoAsync("Disconnecting").ConfigureAwait(false); - await _gatewayLogger.DebugAsync("Disconnecting - CancelToken").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Cancelling current tasks").ConfigureAwait(false); //Signal tasks to complete try { _cancelToken.Cancel(); } catch { } - await _gatewayLogger.DebugAsync("Disconnecting - ApiClient").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); //Disconnect from server await ApiClient.DisconnectAsync().ConfigureAwait(false); //Wait for tasks to complete - await _gatewayLogger.DebugAsync("Disconnecting - Heartbeat").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); var heartbeatTask = _heartbeatTask; if (heartbeatTask != null) await heartbeatTask.ConfigureAwait(false); _heartbeatTask = null; - await _gatewayLogger.DebugAsync("Disconnecting - Guild Downloader").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); var guildDownloadTask = _guildDownloadTask; if (guildDownloadTask != null) await guildDownloadTask.ConfigureAwait(false); _guildDownloadTask = null; //Clear large guild queue - await _gatewayLogger.DebugAsync("Disconnecting - Clean Large Guilds").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); while (_largeGuilds.TryDequeue(out guildId)) { } //Raise virtual GUILD_UNAVAILABLEs + await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); foreach (var guild in State.Guilds) { if (guild._available) @@ -318,7 +328,6 @@ namespace Discord.WebSocket public Task GetApplicationInfoAsync() => ClientHelper.GetApplicationInfoAsync(this); - /// public SocketGuild GetGuild(ulong id) { @@ -329,7 +338,7 @@ namespace Discord.WebSocket => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon); /// - public IChannel GetChannel(ulong id) + public SocketChannel GetChannel(ulong id) { return State.GetChannel(id); } @@ -343,15 +352,38 @@ namespace Discord.WebSocket => ClientHelper.GetInviteAsync(this, inviteId); /// - public IUser GetUser(ulong id) + public SocketUser GetUser(ulong id) { return State.GetUser(id); } /// - public IUser GetUser(string username, string discriminator) + public SocketUser GetUser(string username, string discriminator) { return State.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); } + 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) + { + return state.GetOrAddUser(model.Id, x => + { + var user = SocketGlobalUser.Create(this, state, model); + user.GlobalUser.AddRef(); + user.Presence = new SocketPresence(null, UserStatus.Online); + return user; + }); + } + internal void RemoveUser(ulong id) + { + State.RemoveUser(id); + } /// public RestVoiceRegion GetVoiceRegion(string id) @@ -363,8 +395,8 @@ namespace Discord.WebSocket } /// Downloads the users list for all large guilds. - /*public Task DownloadAllUsersAsync() - => DownloadUsersAsync(DataStore.Guilds.Where(x => !x.HasAllMembers)); + public Task DownloadAllUsersAsync() + => DownloadUsersAsync(State.Guilds.Where(x => !x.HasAllMembers)); /// Downloads the users list for the provided guilds, if they don't have a complete list. public Task DownloadUsersAsync(IEnumerable guilds) => DownloadUsersAsync(guilds.Select(x => x as SocketGuild).Where(x => x != null)); @@ -414,7 +446,7 @@ namespace Discord.WebSocket else await Task.WhenAll(batchTasks).ConfigureAwait(false); } - }*/ + } private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) { @@ -486,26 +518,26 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var dataStore = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); + var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); - var currentUser = SocketSelfUser.Create(this, data.User); + var currentUser = SocketSelfUser.Create(this, state, data.User); int unavailableGuilds = 0; - /*for (int i = 0; i < data.Guilds.Length; i++) + for (int i = 0; i < data.Guilds.Length; i++) { var model = data.Guilds[i]; - var guild = AddGuild(model, dataStore); + var guild = AddGuild(model, state); if (!guild._available || ApiClient.AuthTokenType == TokenType.User) unavailableGuilds++; else await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); } for (int i = 0; i < data.PrivateChannels.Length; i++) - AddPrivateChannel(data.PrivateChannels[i], dataStore);*/ + AddPrivateChannel(data.PrivateChannels[i], state); _sessionId = data.SessionId; - base.CurrentUser = currentUser; _unavailableGuilds = unavailableGuilds; - State = dataStore; + CurrentUser = currentUser; + State = state; } catch (Exception ex) { @@ -525,14 +557,14 @@ namespace Discord.WebSocket await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); } break; - /*case "RESUMED": + case "RESUMED": { await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); var _ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete //Notify the client that these guilds are available again - foreach (var guild in DataStore.Guilds) + foreach (var guild in State.Guilds) { if (guild._available) await _guildAvailableEvent.InvokeAsync(guild).ConfigureAwait(false); @@ -553,10 +585,10 @@ namespace Discord.WebSocket _lastGuildAvailableTime = Environment.TickCount; await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); - var guild = DataStore.GetGuild(data.Id); + var guild = State.GetGuild(data.Id); if (guild != null) { - guild.Update(data, UpdateSource.WebSocket, DataStore); + guild.Update(State, data); var unavailableGuilds = _unavailableGuilds; if (unavailableGuilds != 0) @@ -573,7 +605,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); - var guild = AddGuild(data, DataStore); + var guild = AddGuild(data, State); if (guild != null) { if (ApiClient.AuthTokenType == TokenType.User) @@ -593,11 +625,11 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.Id); + var guild = State.GetGuild(data.Id); if (guild != null) { var before = guild.Clone(); - guild.Update(data, UpdateSource.WebSocket); + guild.Update(State, data); await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); } else @@ -612,11 +644,11 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { var before = guild.Clone(); - guild.Update(data, UpdateSource.WebSocket); + guild.Update(State, data); await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); } else @@ -626,20 +658,15 @@ namespace Discord.WebSocket } } return; - case "GUILD_INTEGRATIONS_UPDATE": - { - await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); - } - return; case "GUILD_SYNC": { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.Id); + var guild = State.GetGuild(data.Id); if (guild != null) { var before = guild.Clone(); - guild.Update(data, UpdateSource.WebSocket, DataStore); + guild.Update(State, data); //This is treated as an extension of GUILD_AVAILABLE _unavailableGuilds--; _lastGuildAvailableTime = Environment.TickCount; @@ -661,11 +688,9 @@ namespace Discord.WebSocket type = "GUILD_UNAVAILABLE"; await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); - var guild = DataStore.GetGuild(data.Id); + var guild = State.GetGuild(data.Id); if (guild != null) { - foreach (var member in guild.Members) - member.User.RemoveRef(this); await _guildUnavailableEvent.InvokeAsync(guild).ConfigureAwait(false); _unavailableGuilds++; } @@ -700,14 +725,13 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - ISocketChannel channel = null; + SocketChannel channel = null; if (data.GuildId.IsSpecified) { - var guild = DataStore.GetGuild(data.GuildId.Value); + var guild = State.GetGuild(data.GuildId.Value); if (guild != null) { - guild.AddChannel(data, DataStore); - channel = guild.AddChannel(data, DataStore); + channel = guild.AddChannel(State, data); if (!guild.IsSynced) { @@ -722,7 +746,7 @@ namespace Discord.WebSocket } } else - channel = AddPrivateChannel(data, DataStore); + channel = AddPrivateChannel(data, State) as SocketChannel; if (channel != null) await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); @@ -733,13 +757,13 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = DataStore.GetChannel(data.Id); + var channel = State.GetChannel(data.Id); if (channel != null) { var before = channel.Clone(); - channel.Update(data, UpdateSource.WebSocket); + channel.Update(State, data); - if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) + if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) { await _gatewayLogger.DebugAsync("Ignored CHANNEL_UPDATE, guild is not synced yet.").ConfigureAwait(false); return; @@ -758,14 +782,14 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); - ISocketChannel channel = null; + SocketChannel channel = null; var data = (payload as JToken).ToObject(_serializer); if (data.GuildId.IsSpecified) { - var guild = DataStore.GetGuild(data.GuildId.Value); + var guild = State.GetGuild(data.GuildId.Value); if (guild != null) { - channel = guild.RemoveChannel(data.Id); + channel = guild.RemoveChannel(State, data.Id); if (!guild.IsSynced) { @@ -780,7 +804,7 @@ namespace Discord.WebSocket } } else - channel = RemovePrivateChannel(data.Id); + channel = RemovePrivateChannel(data.Id) as SocketChannel; if (channel != null) await _channelDestroyedEvent.InvokeAsync(channel).ConfigureAwait(false); @@ -798,10 +822,10 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { - var user = guild.AddOrUpdateUser(data, DataStore); + var user = guild.AddOrUpdateUser(data); guild.MemberCount++; if (!guild.IsSynced) @@ -824,7 +848,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { var user = guild.GetUser(data.User.Id); @@ -838,7 +862,7 @@ namespace Discord.WebSocket if (user != null) { var before = user.Clone(); - user.Update(data, UpdateSource.WebSocket); + user.Update(State, data); await _userUpdatedEvent.InvokeAsync(before, user).ConfigureAwait(false); } else @@ -865,7 +889,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { var user = guild.RemoveUser(data.User.Id); @@ -878,10 +902,7 @@ namespace Discord.WebSocket } if (user != null) - { - user.User.RemoveRef(this); await _userLeftEvent.InvokeAsync(user).ConfigureAwait(false); - } else { if (!guild.HasAllMembers) @@ -906,15 +927,15 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { foreach (var memberModel in data.Members) - guild.AddOrUpdateUser(memberModel, DataStore); + guild.AddOrUpdateUser(memberModel); if (guild.DownloadedMemberCount >= guild.MemberCount) //Finished downloading for there { - guild.CompleteDownloadMembers(); + guild.CompleteDownloadUsers(); await _guildMembersDownloadedEvent.InvokeAsync(guild).ConfigureAwait(false); } } @@ -930,10 +951,10 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = DataStore.GetChannel(data.ChannelId) as SocketGroupChannel; + var channel = State.GetChannel(data.ChannelId) as SocketGroupChannel; if (channel != null) { - var user = channel.AddUser(data.User, DataStore); + var user = channel.AddUser(data.User); await _recipientAddedEvent.InvokeAsync(user).ConfigureAwait(false); } else @@ -948,15 +969,12 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = DataStore.GetChannel(data.ChannelId) as SocketGroupChannel; + var channel = State.GetChannel(data.ChannelId) as SocketGroupChannel; if (channel != null) { var user = channel.RemoveUser(data.User.Id); if (user != null) - { - user.User.RemoveRef(this); await _recipientRemovedEvent.InvokeAsync(user).ConfigureAwait(false); - } else { await _gatewayLogger.WarningAsync("CHANNEL_RECIPIENT_REMOVE referenced an unknown user.").ConfigureAwait(false); @@ -977,7 +995,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { var role = guild.AddRole(data.Role); @@ -1001,14 +1019,14 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { var role = guild.GetRole(data.Role.Id); if (role != null) { var before = role.Clone(); - role.Update(data.Role, UpdateSource.WebSocket); + role.Update(State, data.Role); if (!guild.IsSynced) { @@ -1036,7 +1054,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { var role = guild.RemoveRole(data.RoleId); @@ -1070,7 +1088,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { if (!guild.IsSynced) @@ -1078,8 +1096,8 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Ignored GUILD_BAN_ADD, guild is not synced yet.").ConfigureAwait(false); return; } - - await _userBannedEvent.InvokeAsync(new User(data.User), guild).ConfigureAwait(false); + + await _userBannedEvent.InvokeAsync(SocketSimpleUser.Create(this, State, data.User), guild).ConfigureAwait(false); } else { @@ -1093,7 +1111,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { if (!guild.IsSynced) @@ -1102,7 +1120,10 @@ namespace Discord.WebSocket return; } - await _userUnbannedEvent.InvokeAsync(new User(data.User), guild).ConfigureAwait(false); + SocketUser user = State.GetUser(data.User.Id); + if (user == null) + user = SocketSimpleUser.Create(this, State, data.User); + await _userUnbannedEvent.InvokeAsync(user, guild).ConfigureAwait(false); } else { @@ -1118,20 +1139,28 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; if (channel != null) { - if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) { await _gatewayLogger.DebugAsync("Ignored MESSAGE_CREATE, guild is not synced yet.").ConfigureAwait(false); return; } - var author = channel.GetUser(data.Author.Value.Id, true); + 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 = SocketSimpleUser.Create(this, State, data.Author.Value); if (author != null) { - var msg = channel.AddMessage(author, data); + var msg = SocketMessage.Create(this, State, author, data); + SocketChannelHelper.AddMessage(channel, this, msg); await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); } else @@ -1152,38 +1181,41 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; if (channel != null) { - if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) { await _gatewayLogger.DebugAsync("Ignored MESSAGE_UPDATE, guild is not synced yet.").ConfigureAwait(false); return; } - IMessage before = null, after = null; - ISocketMessage cachedMsg = channel.GetMessage(data.Id); + SocketMessage before = null, after = null; + SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); if (cachedMsg != null) { before = cachedMsg.Clone(); - cachedMsg.Update(data, UpdateSource.WebSocket); + cachedMsg.Update(State, data); after = cachedMsg; } else if (data.Author.IsSpecified) { //Edited message isnt in cache, create a detached one - var author = channel.GetUser(data.Author.Value.Id, true); - if (author != null) - after = channel.CreateMessage(author, data); - } - if (after != null) - await _messageUpdatedEvent.InvokeAsync(Optional.Create(before), after).ConfigureAwait(false); - { - if (before != null) - await _messageUpdatedEvent.InvokeAsync(Optional.Create(before), after).ConfigureAwait(false); + SocketUser author; + if (guild != null) + author = guild.GetUser(data.Author.Value.Id); else - await _messageUpdatedEvent.InvokeAsync(Optional.Create(), after).ConfigureAwait(false); + author = (channel as SocketChannel).GetUser(data.Author.Value.Id); + if (author == null) + author = SocketSimpleUser.Create(this, State, data.Author.Value); + + after = SocketMessage.Create(this, State, author, data); } + if (before != null) + await _messageUpdatedEvent.InvokeAsync(before, after).ConfigureAwait(false); + else + await _messageUpdatedEvent.InvokeAsync(Optional.Create(), after).ConfigureAwait(false); } else { @@ -1197,20 +1229,20 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; if (channel != null) { - if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) + if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) { await _gatewayLogger.DebugAsync("Ignored MESSAGE_DELETE, guild is not synced yet.").ConfigureAwait(false); return; } - var msg = channel.RemoveMessage(data.Id); + var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); if (msg != null) - await _messageDeletedEvent.InvokeAsync(data.Id, Optional.Create(msg)).ConfigureAwait(false); + await _messageDeletedEvent.InvokeAsync(data.Id, msg).ConfigureAwait(false); else - await _messageDeletedEvent.InvokeAsync(data.Id, Optional.Create()).ConfigureAwait(false); + await _messageDeletedEvent.InvokeAsync(data.Id, Optional.Create()).ConfigureAwait(false); } else { @@ -1224,10 +1256,10 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; if (channel != null) { - if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) + if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) { await _gatewayLogger.DebugAsync("Ignored MESSAGE_DELETE_BULK, guild is not synced yet.").ConfigureAwait(false); return; @@ -1235,11 +1267,11 @@ namespace Discord.WebSocket foreach (var id in data.Ids) { - var msg = channel.RemoveMessage(id); + var msg = SocketChannelHelper.RemoveMessage(channel, this, id); if (msg != null) - await _messageDeletedEvent.InvokeAsync(id, Optional.Create(msg)).ConfigureAwait(false); + await _messageDeletedEvent.InvokeAsync(id, msg).ConfigureAwait(false); else - await _messageDeletedEvent.InvokeAsync(id, Optional.Create()).ConfigureAwait(false); + await _messageDeletedEvent.InvokeAsync(id, Optional.Create()).ConfigureAwait(false); } } else @@ -1258,39 +1290,43 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); if (data.GuildId.IsSpecified) { - var guild = DataStore.GetGuild(data.GuildId.Value); + var guild = State.GetGuild(data.GuildId.Value); if (guild == null) { await _gatewayLogger.WarningAsync("PRESENCE_UPDATE referenced an unknown guild.").ConfigureAwait(false); break; } - if (!guild.IsSynced) { await _gatewayLogger.DebugAsync("Ignored PRESENCE_UPDATE, guild is not synced yet.").ConfigureAwait(false); return; } - IPresence before; - var user = guild.GetUser(data.User.Id); + SocketPresence before; + SocketUser user = guild.GetUser(data.User.Id); if (user != null) { before = user.Presence.Clone(); - user.Update(data, UpdateSource.WebSocket); + user.Update(State, data); } else { before = new SocketPresence(null, UserStatus.Offline); - user = guild.AddOrUpdateUser(data, DataStore); + user = guild.AddOrUpdateUser(data); } - await _userPresenceUpdatedEvent.InvokeAsync(user, before, user).ConfigureAwait(false); + await _userPresenceUpdatedEvent.InvokeAsync(user, before, user.Presence).ConfigureAwait(false); } else { - var channel = DataStore.GetDMChannel(data.User.Id); + var channel = State.GetChannel(data.User.Id); if (channel != null) - channel.Recipient.Update(data, UpdateSource.WebSocket); + { + var user = channel.GetUser(data.User.Id); + var before = user.Presence.Clone(); + user.Update(State, data); + await _userPresenceUpdatedEvent.InvokeAsync(user, before, user.Presence).ConfigureAwait(false); + } } } break; @@ -1299,16 +1335,16 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = DataStore.GetChannel(data.ChannelId) as ISocketMessageChannel; + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; if (channel != null) { - if (!((channel as ISocketGuildChannel)?.Guild.IsSynced ?? true)) + if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) { await _gatewayLogger.DebugAsync("Ignored TYPING_START, guild is not synced yet.").ConfigureAwait(false); return; } - var user = channel.GetUser(data.UserId, true); + var user = (channel as SocketChannel).GetUser(data.UserId); if (user != null) await _userIsTypingEvent.InvokeAsync(user, channel).ConfigureAwait(false); } @@ -1324,7 +1360,7 @@ namespace Discord.WebSocket if (data.Id == CurrentUser.Id) { var before = CurrentUser.Clone(); - CurrentUser.Update(data, UpdateSource.WebSocket); + CurrentUser.Update(State, data); await _selfUpdatedEvent.InvokeAsync(before, CurrentUser).ConfigureAwait(false); } else @@ -1343,56 +1379,53 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); if (data.GuildId.HasValue) { - ISocketUser user; + SocketUser user; SocketVoiceState before, after; if (data.GuildId != null) { - var guild = DataStore.GetGuild(data.GuildId.Value); - if (guild != null) + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) { - if (!guild.IsSynced) - { - await _gatewayLogger.DebugAsync("Ignored VOICE_STATE_UPDATE, guild is not synced yet.").ConfigureAwait(false); - return; - } + await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false); + return; + } + else if (!guild.IsSynced) + { + await _gatewayLogger.DebugAsync("Ignored VOICE_STATE_UPDATE, guild is not synced yet.").ConfigureAwait(false); + return; + } - if (data.ChannelId != null) - { - before = guild.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); - after = guild.AddOrUpdateVoiceState(data, DataStore); - if (data.UserId == _currentUser.Id) - { - var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false); - } - } - else + if (data.ChannelId != null) + { + before = guild.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); + after = guild.AddOrUpdateVoiceState(State, data); + if (data.UserId == CurrentUser.Id) { - before = guild.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); - after = new SocketVoiceState(null, data); + var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false); } - - user = guild.GetUser(data.UserId); } else { - await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false); - return; + before = guild.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); + after = SocketVoiceState.Create(null, data); } + + user = guild.GetUser(data.UserId); } else { - var groupChannel = DataStore.GetChannel(data.ChannelId.Value) as SocketGroupChannel; + var groupChannel = State.GetChannel(data.ChannelId.Value) as SocketGroupChannel; if (groupChannel != null) { if (data.ChannelId != null) { before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new SocketVoiceState(null, null, false, false, false); - after = groupChannel.AddOrUpdateVoiceState(data, DataStore); + after = groupChannel.AddOrUpdateVoiceState(State, data); } else { before = groupChannel.RemoveVoiceState(data.UserId) ?? new SocketVoiceState(null, null, false, false, false); - after = new SocketVoiceState(null, data); + after = SocketVoiceState.Create(null, data); } user = groupChannel.GetUser(data.UserId); } @@ -1419,7 +1452,7 @@ namespace Discord.WebSocket if (AudioMode != AudioMode.Disabled) { var data = (payload as JToken).ToObject(_serializer); - var guild = DataStore.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); if (guild != null) { string endpoint = data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':')); @@ -1431,8 +1464,7 @@ namespace Discord.WebSocket return; } } - - return;*/ + return; //Ignored (User only) case "CHANNEL_PINS_ACK": @@ -1441,12 +1473,15 @@ namespace Discord.WebSocket case "CHANNEL_PINS_UPDATE": await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_UPDATE)"); break; - case "USER_SETTINGS_UPDATE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); - return; case "MESSAGE_ACK": await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); return; + case "GUILD_INTEGRATIONS_UPDATE": + await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); + return; + case "USER_SETTINGS_UPDATE": + await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); + return; //Others default: @@ -1532,6 +1567,42 @@ namespace Discord.WebSocket await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false); } + internal SocketGuild AddGuild(ExtendedGuild model, ClientState state) + { + var guild = SocketGuild.Create(this, state, model); + state.AddGuild(guild); + if (model.Large) + _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); + } + return guild; + } + + internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state) + { + return SocketChannel.CreatePrivate(this, state, model); + } + internal ISocketPrivateChannel RemovePrivateChannel(ulong id) + { + var channel = State.RemoveChannel(id) as ISocketPrivateChannel; + if (channel != null) + { + foreach (var recipient in channel.Recipients) + recipient.GlobalUser.RemoveRef(this); + } + return channel; + } + //IDiscordClient DiscordRestApiClient IDiscordClient.ApiClient => ApiClient; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketAudioChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketAudioChannel.cs new file mode 100644 index 000000000..7056a4df5 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketAudioChannel.cs @@ -0,0 +1,6 @@ +namespace Discord.WebSocket +{ + public interface ISocketAudioChannel : IAudioChannel + { + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs new file mode 100644 index 000000000..a555754d2 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -0,0 +1,32 @@ +using Discord.Rest; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + public interface ISocketMessageChannel : IMessageChannel + { + /// Gets all messages in this channel's cache. + IReadOnlyCollection CachedMessages { get; } + + /// Sends a message to this message channel. + new Task SendMessageAsync(string text, bool isTTS = false); + /// Sends a file to this text channel, with an optional caption. + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false); + /// Sends a file to this text channel, with an optional caption. + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); + + SocketMessage GetCachedMessage(ulong id); + /// Gets a message from this message channel with the given id, or null if not found. + Task GetMessageAsync(ulong id); + /// Gets the last N messages from this message channel. + Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of messages in this channel. + Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of messages in this channel. + Task> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of pinned messages in this channel. + new Task> GetPinnedMessagesAsync(); + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketPrivateChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketPrivateChannel.cs new file mode 100644 index 000000000..4e91673dd --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketPrivateChannel.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Discord.WebSocket +{ + public interface ISocketPrivateChannel : IPrivateChannel + { + new IReadOnlyCollection Recipients { get; } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index c9f38caec..07f9f5073 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -11,35 +11,37 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class SocketChannel : SocketEntity, IChannel { + public IReadOnlyCollection Users => GetUsersInternal(); + internal SocketChannel(DiscordSocketClient discord, ulong id) : base(discord, id) { } - internal static SocketChannel Create(DiscordSocketClient discord, Model model) + internal static ISocketPrivateChannel CreatePrivate(DiscordSocketClient discord, ClientState state, Model model) { switch (model.Type) { - case ChannelType.Text: - return SocketTextChannel.Create(discord, model); - case ChannelType.Voice: - return SocketVoiceChannel.Create(discord, model); case ChannelType.DM: - return SocketDMChannel.Create(discord, model); + return SocketDMChannel.Create(discord, state, model); case ChannelType.Group: - return SocketGroupChannel.Create(discord, model); + return SocketGroupChannel.Create(discord, state, model); default: throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); } } + internal abstract void Update(ClientState state, Model model); + + //User + public SocketUser GetUser(ulong id) => GetUserInternal(id); + internal abstract SocketUser GetUserInternal(ulong id); + internal abstract IReadOnlyCollection GetUsersInternal(); - //IChannel - IReadOnlyCollection IChannel.CachedUsers => ImmutableArray.Create(); + internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; - IUser IChannel.GetCachedUser(ulong id) - => null; - Task IChannel.GetUserAsync(ulong id) - => Task.FromResult(null); - IAsyncEnumerable> IChannel.GetUsersAsync() - => ImmutableArray.Create>().ToAsyncEnumerable(); + //IChannel + Task IChannel.GetUserAsync(ulong id, CacheMode mode) + => Task.FromResult(null); //Overridden + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + => ImmutableArray.Create>().ToAsyncEnumerable(); //Overridden } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs new file mode 100644 index 000000000..856acdc73 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -0,0 +1,81 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + internal static class SocketChannelHelper + { + public static async Task> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, + ulong? fromMessageId, Direction dir, int limit) + { + if (messages == null) //Cache disabled + { + var msgs = await ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit).Flatten(); + return msgs.ToImmutableArray(); + } + + var cachedMessages = messages.GetMany(fromMessageId, dir, limit); + limit -= cachedMessages.Count; + if (limit == 0) + return cachedMessages; + + if (dir == Direction.Before) + fromMessageId = cachedMessages.Min(x => x.Id); + else + fromMessageId = cachedMessages.Max(x => x.Id); + var downloadedMessages = await ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit).Flatten(); + return cachedMessages.Concat(downloadedMessages).ToImmutableArray(); + } + + public static IAsyncEnumerable> PagedGetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, + ulong? fromMessageId, Direction dir, int limit) + { + if (messages == null) //Cache disabled + return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit); + + var cachedMessages = messages.GetMany(fromMessageId, dir, limit); + var result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>(); + limit -= cachedMessages.Count; + if (limit == 0) + return result; + + if (dir == Direction.Before) + fromMessageId = cachedMessages.Min(x => x.Id); + else + fromMessageId = cachedMessages.Max(x => x.Id); + var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit); + return result.Concat(downloadedMessages); + } + + public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, + SocketMessage msg) + { + //C#7 Candidate for pattern matching + if (channel is SocketDMChannel) + (channel as SocketDMChannel).AddMessage(msg); + else if (channel is SocketGroupChannel) + (channel as SocketDMChannel).AddMessage(msg); + else if (channel is SocketTextChannel) + (channel as SocketDMChannel).AddMessage(msg); + else + throw new NotSupportedException("Unexpected ISocketMessageChannel type"); + } + public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord, + ulong id) + { + //C#7 Candidate for pattern matching + if (channel is SocketDMChannel) + return (channel as SocketDMChannel).RemoveMessage(id); + else if (channel is SocketGroupChannel) + return (channel as SocketDMChannel).RemoveMessage(id); + else if (channel is SocketTextChannel) + return (channel as SocketDMChannel).RemoveMessage(id); + else + throw new NotSupportedException("Unexpected ISocketMessageChannel type"); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 6b87fbae2..0a747b446 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -12,60 +12,52 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class SocketDMChannel : SocketChannel, IDMChannel + public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel { private readonly MessageCache _messages; public SocketUser Recipient { get; private set; } - public IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); + public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); + public new IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); - internal SocketDMChannel(DiscordSocketClient discord, ulong id, ulong recipientId) + internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketGlobalUser recipient) : base(discord, id) { - Recipient = new SocketUser(Discord, recipientId); + Recipient = recipient; if (Discord.MessageCacheSize > 0) _messages = new MessageCache(Discord, this); } - internal new static SocketDMChannel Create(DiscordSocketClient discord, Model model) + internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) { - var entity = new SocketDMChannel(discord, model.Id, model.Recipients.Value[0].Id); - entity.Update(model); + var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0])); + entity.Update(state, model); return entity; } - internal void Update(Model model) + internal override void Update(ClientState state, Model model) { - Recipient.Update(model.Recipients.Value[0]); + Recipient.Update(state, model.Recipients.Value[0]); } public Task CloseAsync() => ChannelHelper.DeleteAsync(this, Discord); - public SocketUser GetUser(ulong id) - { - if (id == Recipient.Id) - return Recipient; - else if (id == Discord.CurrentUser.Id) - return Discord.CurrentUser as SocketSelfUser; - else - return null; - } - - public SocketMessage GetMessage(ulong id) + //Messages + public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); - public async Task GetMessageAsync(ulong id, bool allowDownload = true) + public async Task GetMessageAsync(ulong id) { IMessage msg = _messages?.Get(id); - if (msg == null && allowDownload) + if (msg == null) msg = await ChannelHelper.GetMessageAsync(this, Discord, id); return msg; } - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + public Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); + public Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); + public Task> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); public Task> GetPinnedMessagesAsync() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -82,38 +74,61 @@ namespace Discord.WebSocket public IDisposable EnterTypingState() => ChannelHelper.EnterTypingState(this, Discord); - internal SocketMessage AddMessage(SocketUser author, MessageModel model) - { - var msg = SocketMessage.Create(Discord, author, model); - _messages.Add(msg); - return msg; - } + internal void AddMessage(SocketMessage msg) + => _messages.Add(msg); internal SocketMessage RemoveMessage(ulong id) + => _messages.Remove(id); + + //Users + public new SocketUser GetUser(ulong id) { - return _messages.Remove(id); + if (id == Recipient.Id) + return Recipient; + else if (id == Discord.CurrentUser.Id) + return Discord.CurrentUser as SocketSelfUser; + else + return null; } - public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; - 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 - IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); - IMessage IMessageChannel.GetCachedMessage(ulong id) => null; - - async Task IMessageChannel.GetMessageAsync(ulong id) - => await GetMessageAsync(id); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) - => GetMessagesAsync(limit); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - => GetMessagesAsync(fromMessageId, dir, limit); + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id); + else + return GetCachedMessage(id); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); + else + return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); + else + return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) @@ -125,14 +140,10 @@ namespace Discord.WebSocket IDisposable IMessageChannel.EnterTypingState() => EnterTypingState(); - //IChannel - IReadOnlyCollection IChannel.CachedUsers => Users; - - IUser IChannel.GetCachedUser(ulong id) - => GetUser(id); - Task IChannel.GetUserAsync(ulong id) + //IChannel + Task IChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync() - => ImmutableArray.Create>().ToAsyncEnumerable(); + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + => 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 7dc81fbb6..346f303cd 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; -using MessageModel = Discord.API.Message; using Model = Discord.API.Channel; using UserModel = Discord.API.User; using VoiceStateModel = Discord.API.VoiceState; @@ -15,7 +14,7 @@ using VoiceStateModel = Discord.API.VoiceState; namespace Discord.WebSocket { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class SocketGroupChannel : SocketChannel, IGroupChannel + public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel { private readonly MessageCache _messages; @@ -25,7 +24,8 @@ namespace Discord.WebSocket public string Name { get; private set; } - public IReadOnlyCollection Users => _users.ToReadOnlyCollection(); + 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); @@ -37,13 +37,13 @@ namespace Discord.WebSocket _voiceStates = new ConcurrentDictionary(1, 5); _users = new ConcurrentDictionary(1, 5); } - internal new static SocketGroupChannel Create(DiscordSocketClient discord, Model model) + internal static SocketGroupChannel Create(DiscordSocketClient discord, ClientState state, Model model) { var entity = new SocketGroupChannel(discord, model.Id); - entity.Update(model); + entity.Update(state, model); return entity; } - internal void Update(Model model) + internal override void Update(ClientState state, Model model) { if (model.Name.IsSpecified) Name = model.Name.Value; @@ -51,37 +51,35 @@ namespace Discord.WebSocket _iconId = model.Icon.Value; if (model.Recipients.IsSpecified) - UpdateUsers(model.Recipients.Value); + UpdateUsers(state, model.Recipients.Value); } - internal virtual void UpdateUsers(API.User[] models) + internal virtual void UpdateUsers(ClientState state, UserModel[] models) { var users = new ConcurrentDictionary(1, (int)(models.Length * 1.05)); for (int i = 0; i < models.Length; i++) - users[models[i].Id] = SocketGroupUser.Create(Discord, models[i]); + users[models[i].Id] = SocketGroupUser.Create(this, state, models[i]); _users = users; } - - public async Task UpdateAsync() - => Update(await ChannelHelper.GetAsync(this, Discord)); + public Task LeaveAsync() => ChannelHelper.DeleteAsync(this, Discord); - public SocketGroupUser GetUser(ulong id) + //Messages + public SocketMessage GetCachedMessage(ulong id) + => _messages?.Get(id); + public async Task GetMessageAsync(ulong id) { - SocketGroupUser user; - if (_users.TryGetValue(id, out user)) - return user; - return null; + IMessage msg = _messages?.Get(id); + if (msg == null) + msg = await ChannelHelper.GetMessageAsync(this, Discord, id); + return msg; } - - public Task GetMessageAsync(ulong id) - => ChannelHelper.GetMessageAsync(this, Discord, id); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + public Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); + public Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); + public Task> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); public Task> GetPinnedMessagesAsync() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -98,20 +96,101 @@ namespace Discord.WebSocket public IDisposable EnterTypingState() => ChannelHelper.EnterTypingState(this, Discord); + internal void AddMessage(SocketMessage msg) + => _messages.Add(msg); + internal SocketMessage RemoveMessage(ulong id) + => _messages.Remove(id); + + //Users + public new SocketGroupUser GetUser(ulong id) + { + SocketGroupUser user; + if (_users.TryGetValue(id, out user)) + return user; + return null; + } + internal SocketGroupUser AddUser(UserModel model) + { + SocketGroupUser user; + if (_users.TryGetValue(model.Id, out user)) + return user as SocketGroupUser; + else + { + var privateUser = SocketGroupUser.Create(this, Discord.State, model); + _users[privateUser.Id] = privateUser; + return privateUser; + } + } + internal SocketGroupUser RemoveUser(ulong id) + { + SocketGroupUser user; + if (_users.TryRemove(id, out user)) + { + user.GlobalUser.RemoveRef(Discord); + return user as SocketGroupUser; + } + return null; + } + + //Voice States + internal SocketVoiceState AddOrUpdateVoiceState(ClientState state, VoiceStateModel model) + { + var voiceChannel = state.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; + var voiceState = SocketVoiceState.Create(voiceChannel, model); + _voiceStates[model.UserId] = voiceState; + return voiceState; + } + internal SocketVoiceState? GetVoiceState(ulong id) + { + SocketVoiceState voiceState; + if (_voiceStates.TryGetValue(id, out voiceState)) + return voiceState; + return null; + } + internal SocketVoiceState? RemoveVoiceState(ulong id) + { + SocketVoiceState voiceState; + if (_voiceStates.TryRemove(id, out 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 - IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); - - IMessage IMessageChannel.GetCachedMessage(ulong id) - => null; - async Task IMessageChannel.GetMessageAsync(ulong id) - => await GetMessageAsync(id); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) - => GetMessagesAsync(limit); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - => GetMessagesAsync(fromMessageId, dir, limit); + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id); + else + return GetCachedMessage(id); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); + else + return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); + else + return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); @@ -124,14 +203,10 @@ namespace Discord.WebSocket IDisposable IMessageChannel.EnterTypingState() => EnterTypingState(); - //IChannel - IReadOnlyCollection IChannel.CachedUsers => Users; - - IUser IChannel.GetCachedUser(ulong id) - => GetUser(id); - Task IChannel.GetUserAsync(ulong id) + //IChannel + Task IChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync() + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) => 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 8821ff616..c3a33c3ea 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -15,31 +15,31 @@ namespace Discord.WebSocket { private ImmutableArray _overwrites; - public IReadOnlyCollection PermissionOverwrites => _overwrites; - - public ulong GuildId { get; } - + public SocketGuild Guild { get; } public string Name { get; private set; } public int Position { get; private set; } - internal SocketGuildChannel(DiscordSocketClient discord, ulong id, ulong guildId) + public IReadOnlyCollection PermissionOverwrites => _overwrites; + public new abstract IReadOnlyCollection Users { get; } + + internal SocketGuildChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id) { - GuildId = guildId; + Guild = guild; } - internal new static SocketGuildChannel Create(DiscordSocketClient discord, Model model) + internal static SocketGuildChannel Create(SocketGuild guild, ClientState state, Model model) { switch (model.Type) { case ChannelType.Text: - return SocketTextChannel.Create(discord, model); + return SocketTextChannel.Create(guild, state, model); case ChannelType.Voice: - return SocketVoiceChannel.Create(discord, model); + return SocketVoiceChannel.Create(guild, state, model); default: throw new InvalidOperationException("Unknown guild channel type"); } } - internal virtual void Update(Model model) + internal override void Update(ClientState state, Model model) { Name = model.Name.Value; Position = model.Position.Value; @@ -50,9 +50,7 @@ namespace Discord.WebSocket newOverwrites.Add(new Overwrite(overwrites[i])); _overwrites = newOverwrites.ToImmutable(); } - - public async Task UpdateAsync() - => Update(await ChannelHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) => ChannelHelper.ModifyAsync(this, Discord, func); public Task DeleteAsync() @@ -118,7 +116,18 @@ namespace Discord.WebSocket public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); + public new abstract SocketGuildUser GetUser(ulong id); + + public override string ToString() => Name; + internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; + + //SocketChannel + internal override IReadOnlyCollection GetUsersInternal() => Users; + internal override SocketUser GetUserInternal(ulong id) => GetUser(id); + //IGuildChannel + ulong IGuildChannel.GuildId => Guild.Id; + async Task> IGuildChannel.GetInvitesAsync() => await GetInvitesAsync(); async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) @@ -136,24 +145,16 @@ namespace Discord.WebSocket => await RemovePermissionOverwriteAsync(role); async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) => await RemovePermissionOverwriteAsync(user); - - IReadOnlyCollection IGuildChannel.CachedUsers - => ImmutableArray.Create(); - IAsyncEnumerable> IGuildChannel.GetUsersAsync() - => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? - Task IGuildChannel.GetUserAsync(ulong id) - => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? - IGuildUser IGuildChannel.GetCachedUser(ulong id) - => null; + + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + => Task.FromResult(GetUser(id)); //IChannel - IReadOnlyCollection IChannel.CachedUsers - => ImmutableArray.Create(); - IUser IChannel.GetCachedUser(ulong id) - => null; - IAsyncEnumerable> IChannel.GetUsersAsync() - => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? - Task IChannel.GetUserAsync(ulong id) - => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? + Task IChannel.GetUserAsync(ulong id, CacheMode mode) + => Task.FromResult(GetUser(id)); //Overriden in Text/Voice //TODO: Does this actually override? } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 39a392ef2..c1f5ca828 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -13,29 +13,34 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class SocketTextChannel : SocketGuildChannel, ITextChannel + public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel { private readonly MessageCache _messages; public string Topic { get; private set; } public string Mention => MentionUtils.MentionChannel(Id); - - internal SocketTextChannel(DiscordSocketClient discord, ulong id, ulong guildId) - : base(discord, id, guildId) + 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.ReadMessages)).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(DiscordSocketClient discord, Model model) + internal new static SocketTextChannel Create(SocketGuild guild, ClientState state, Model model) { - var entity = new SocketTextChannel(discord, model.Id, model.GuildId.Value); - entity.Update(model); + var entity = new SocketTextChannel(guild.Discord, model.Id, guild); + entity.Update(state, model); return entity; } - internal override void Update(Model model) + internal override void Update(ClientState state, Model model) { - base.Update(model); + base.Update(state, model); Topic = model.Topic.Value; } @@ -43,19 +48,22 @@ namespace Discord.WebSocket public Task ModifyAsync(Action func) => ChannelHelper.ModifyAsync(this, Discord, func); - public Task GetUserAsync(ulong id) - => ChannelHelper.GetUserAsync(this, Discord, id); - public IAsyncEnumerable> GetUsersAsync() - => ChannelHelper.GetUsersAsync(this, Discord); - - public Task GetMessageAsync(ulong id) - => ChannelHelper.GetMessageAsync(this, Discord, id); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); + //Messages + public SocketMessage GetCachedMessage(ulong id) + => _messages?.Get(id); + public async Task GetMessageAsync(ulong id) + { + IMessage msg = _messages?.Get(id); + if (msg == null) + msg = await ChannelHelper.GetMessageAsync(this, Discord, id); + return msg; + } + public Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); + public Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); + public Task> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); public Task> GetPinnedMessagesAsync() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -72,36 +80,56 @@ namespace Discord.WebSocket public IDisposable EnterTypingState() => ChannelHelper.EnterTypingState(this, Discord); - internal SocketMessage AddMessage(SocketUser author, MessageModel model) - { - var msg = SocketMessage.Create(Discord, author, model); - _messages.Add(msg); - return msg; - } + internal void AddMessage(SocketMessage msg) + => _messages.Add(msg); internal SocketMessage RemoveMessage(ulong id) + => _messages.Remove(id); + + //Users + public override SocketGuildUser GetUser(ulong id) { - return _messages.Remove(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.ReadMessages)) + return user; + } + return null; } - - public override string ToString() => Name; - private string DebuggerDisplay => $"@{Name} ({Id}, Text)"; + + private string DebuggerDisplay => $"{Name} ({Id}, Text)"; + internal new SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; //IGuildChannel - async Task IGuildChannel.GetUserAsync(ulong id) - => await GetUserAsync(id); - IAsyncEnumerable> IGuildChannel.GetUsersAsync() - => GetUsersAsync(); + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + => Task.FromResult(GetUser(id)); + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //IMessageChannel - IReadOnlyCollection IMessageChannel.CachedMessages => ImmutableArray.Create(); - IMessage IMessageChannel.GetCachedMessage(ulong id) => null; - - async Task IMessageChannel.GetMessageAsync(ulong id) - => await GetMessageAsync(id); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit) - => GetMessagesAsync(limit); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) - => GetMessagesAsync(fromMessageId, dir, limit); + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id); + else + throw new NotImplementedException(); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); + else + return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); + else + return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 808f07e58..62f46cf60 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -12,24 +12,27 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel + public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel { public int Bitrate { get; private set; } public int UserLimit { get; private set; } - internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, ulong guildId) - : base(discord, id, guildId) + 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) { } - internal new static SocketVoiceChannel Create(DiscordSocketClient discord, Model model) + internal new static SocketVoiceChannel Create(SocketGuild guild, ClientState state, Model model) { - var entity = new SocketVoiceChannel(discord, model.Id, model.GuildId.Value); - entity.Update(model); + var entity = new SocketVoiceChannel(guild.Discord, model.Id, guild); + entity.Update(state, model); return entity; } - internal override void Update(Model model) + internal override void Update(ClientState state, Model model) { - base.Update(model); + base.Update(state, model); Bitrate = model.Bitrate.Value; UserLimit = model.UserLimit.Value; @@ -38,13 +41,24 @@ namespace Discord.WebSocket public Task ModifyAsync(Action func) => ChannelHelper.ModifyAsync(this, Discord, func); + public override SocketGuildUser GetUser(ulong id) + { + var user = Guild.GetUser(id); + if (user?.VoiceChannel?.Id == Id) + return user; + return null; + } + + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; + internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; + //IVoiceChannel Task IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } //IGuildChannel - Task IGuildChannel.GetUserAsync(ulong id) - => Task.FromResult(null); - IAsyncEnumerable> IGuildChannel.GetUsersAsync() - => ImmutableArray.Create>().ToAsyncEnumerable(); + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + => Task.FromResult(GetUser(id)); + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 956596ace..45c1a588d 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -22,7 +22,14 @@ namespace Discord.WebSocket { public class SocketGuild : SocketEntity, IGuild { - private ImmutableDictionary _roles; + private readonly SemaphoreSlim _audioLock; + private TaskCompletionSource _syncPromise, _downloaderPromise; + private TaskCompletionSource _audioConnectPromise; + private ConcurrentHashSet _channels; + private ConcurrentDictionary _members; + private ConcurrentDictionary _roles; + private ConcurrentDictionary _voiceStates; + private ConcurrentDictionary _cachedPresences; private ImmutableArray _emojis; private ImmutableArray _features; internal bool _available; @@ -33,6 +40,9 @@ namespace Discord.WebSocket public VerificationLevel VerificationLevel { get; private set; } public MfaLevel MfaLevel { get; private set; } public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } + public int MemberCount { get; set; } + public int DownloadedMemberCount { get; private set; } + public AudioClient AudioClient { get; private set; } public ulong? AFKChannelId { get; private set; } public ulong? EmbedChannelId { get; private set; } @@ -44,24 +54,117 @@ namespace Discord.WebSocket public ulong DefaultChannelId => Id; public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId); - public bool IsSynced => false; + public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; + public bool IsSynced => _syncPromise.Task.IsCompleted; + public Task SyncPromise => _syncPromise.Task; + public Task DownloaderPromise => _downloaderPromise.Task; - public RestRole EveryoneRole => GetRole(Id); - public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + 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); + } + } public IReadOnlyCollection Emojis => _emojis; public IReadOnlyCollection Features => _features; + public IReadOnlyCollection Users => _members.ToReadOnlyCollection(); + public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + public IReadOnlyCollection VoiceStates => _voiceStates.ToReadOnlyCollection(); internal SocketGuild(DiscordSocketClient client, ulong id) : base(client, id) { + _emojis = ImmutableArray.Create(); + _features = ImmutableArray.Create(); } - internal static SocketGuild Create(DiscordSocketClient discord, Model model) + internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model) { var entity = new SocketGuild(discord, model.Id); - entity.Update(model); + entity.Update(state, model); return entity; } - internal void Update(Model model) + internal void Update(ClientState state, ExtendedModel model) + { + _available = !(model.Unavailable ?? false); + if (!_available) + { + if (_channels == null) + _channels = new ConcurrentHashSet(); + if (_members == null) + _members = new ConcurrentDictionary(); + if (_roles == null) + _roles = new ConcurrentDictionary(); + /*if (Emojis == null) + _emojis = ImmutableArray.Create(); + if (Features == null) + _features = ImmutableArray.Create();*/ + return; + } + + Update(state, model as Model); + + var channels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05)); + { + for (int i = 0; i < model.Channels.Length; i++) + { + var channel = SocketGuildChannel.Create(this, state, model.Channels[i]); + state.AddChannel(channel); + channels.TryAdd(channel.Id); + } + } + _channels = channels; + + var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); + { + for (int i = 0; i < model.Members.Length; i++) + { + var member = SocketGuildUser.Create(this, state, model.Members[i]); + members.TryAdd(member.Id, member); + } + DownloadedMemberCount = members.Count; + } + var cachedPresences = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Presences.Length * 1.05)); + { + for (int i = 0; i < model.Presences.Length; i++) + { + SocketGuildUser member; + if (_members.TryGetValue(model.Presences[i].User.Id, out member)) + member.Update(state, model.Presences[i]); + else + cachedPresences.TryAdd(model.Presences[i].User.Id, model.Presences[i]); + } + } + _members = members; + _cachedPresences = cachedPresences; + MemberCount = model.MemberCount; + + var voiceStates = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.VoiceStates.Length * 1.05)); + { + for (int i = 0; i < model.VoiceStates.Length; i++) + { + 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); + } + } + _voiceStates = voiceStates; + + _syncPromise = new TaskCompletionSource(); + _downloaderPromise = new TaskCompletionSource(); + if (Discord.ApiClient.AuthTokenType != TokenType.User) + { + var _ = _syncPromise.TrySetResultAsync(true); + if (!model.Large) + _ = _downloaderPromise.TrySetResultAsync(true); + } + } + internal void Update(ClientState state, Model model) { AFKChannelId = model.AFKChannelId; EmbedChannelId = model.EmbedChannelId; @@ -81,7 +184,7 @@ namespace Discord.WebSocket var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); for (int i = 0; i < model.Emojis.Length; i++) emojis.Add(Emoji.Create(model.Emojis[i])); - _emojis = emojis.ToImmutableArray(); + _emojis = emojis.ToImmutable(); } else _emojis = ImmutableArray.Create(); @@ -91,17 +194,52 @@ namespace Discord.WebSocket else _features = ImmutableArray.Create(); - var roles = ImmutableDictionary.CreateBuilder(); + var roles = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05)); if (model.Roles != null) { - throw new NotImplementedException(); + for (int i = 0; i < model.Roles.Length; i++) + { + var role = SocketRole.Create(this, state, model.Roles[i]); + roles.TryAdd(role.Id, role); + } } - _roles = roles.ToImmutable(); + _roles = roles; + } + internal void Update(ClientState state, GuildSyncModel model) + { + var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); + { + for (int i = 0; i < model.Members.Length; i++) + { + var member = SocketGuildUser.Create(this, state, model.Members[i]); + members.TryAdd(member.Id, member); + } + DownloadedMemberCount = members.Count; + } + var cachedPresences = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Presences.Length * 1.05)); + { + for (int i = 0; i < model.Presences.Length; i++) + { + SocketGuildUser member; + if (_members.TryGetValue(model.Presences[i].User.Id, out member)) + member.Update(state, model.Presences[i]); + else + cachedPresences.TryAdd(model.Presences[i].User.Id, model.Presences[i]); + } + } + _members = members; + _cachedPresences = cachedPresences; + } + + internal void Update(ClientState state, EmojiUpdateModel model) + { + var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); + for (int i = 0; i < model.Emojis.Length; i++) + emojis.Add(Emoji.Create(model.Emojis[i])); + _emojis = emojis.ToImmutable(); } //General - public async Task UpdateAsync() - => Update(await Discord.ApiClient.GetGuildAsync(Id)); public Task DeleteAsync() => GuildHelper.DeleteAsync(this, Discord); @@ -132,14 +270,30 @@ namespace Discord.WebSocket => GuildHelper.RemoveBanAsync(this, Discord, userId); //Channels - public Task> GetChannelsAsync() - => GuildHelper.GetChannelsAsync(this, Discord); - public Task GetChannelAsync(ulong id) - => GuildHelper.GetChannelAsync(this, Discord, id); + public SocketGuildChannel GetChannel(ulong id) + { + var channel = Discord.State.GetChannel(id) as SocketGuildChannel; + if (channel?.Guild.Id == Id) + return channel; + return null; + } public Task CreateTextChannelAsync(string name) => GuildHelper.CreateTextChannelAsync(this, Discord, name); public Task CreateVoiceChannelAsync(string name) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name); + internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) + { + var channel = SocketGuildChannel.Create(this, state, model); + _channels.TryAdd(model.Id); + state.AddChannel(channel); + return channel; + } + internal SocketGuildChannel RemoveChannel(ClientState state, ulong id) + { + if (_channels.TryRemove(id)) + return state.RemoveChannel(id) as SocketGuildChannel; + return null; + } //Integrations public Task> GetIntegrationsAsync() @@ -152,48 +306,238 @@ namespace Discord.WebSocket => GuildHelper.GetInvitesAsync(this, Discord); //Roles - public RestRole GetRole(ulong id) + public SocketRole GetRole(ulong id) { - RestRole value; + SocketRole value; if (_roles.TryGetValue(id, out value)) return value; return null; } - - public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) + public Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) + => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); + internal SocketRole AddRole(RoleModel model) { - var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); - _roles = _roles.Add(role.Id, role); + var role = SocketRole.Create(this, Discord.State, model); + _roles[model.Id] = role; return role; } + internal SocketRole RemoveRole(ulong id) + { + SocketRole role; + if (_roles.TryRemove(id, out role)) + return role; + return null; + } //Users - public Task> GetUsersAsync() - => GuildHelper.GetUsersAsync(this, Discord); - public Task GetUserAsync(ulong id) - => GuildHelper.GetUserAsync(this, Discord, id); - public Task GetCurrentUserAsync() - => GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id); - + public SocketGuildUser GetUser(ulong id) + { + SocketGuildUser member; + if (_members.TryGetValue(id, out member)) + return member; + return null; + } + public SocketGuildUser GetCurrentUser() + { + SocketGuildUser member; + if (_members.TryGetValue(Discord.CurrentUser.Id, out member)) + return member; + return null; + } public Task PruneUsersAsync(int days = 30, bool simulate = false) => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); + internal SocketGuildUser AddOrUpdateUser(MemberModel model) + { + SocketGuildUser member; + if (_members.TryGetValue(model.User.Id, out member)) + member.Update(Discord.State, model); + else + { + member = SocketGuildUser.Create(this, Discord.State, model); + _members[member.Id] = member; + DownloadedMemberCount++; + } + return member; + } + internal SocketGuildUser AddOrUpdateUser(PresenceModel model) + { + SocketGuildUser member; + if (_members.TryGetValue(model.User.Id, out member)) + member.Update(Discord.State, model); + else + { + member = SocketGuildUser.Create(this, Discord.State, model); + _members[member.Id] = member; + DownloadedMemberCount++; + } + return member; + } + internal SocketGuildUser RemoveUser(ulong id) + { + SocketGuildUser member; + if (_members.TryRemove(id, out member)) + { + DownloadedMemberCount--; + return member; + } + member.GlobalUser.RemoveRef(Discord); + return null; + } + + public async Task DownloadUsersAsync() + { + await Discord.DownloadUsersAsync(new[] { this }); + } + internal void CompleteDownloadUsers() + { + _downloaderPromise.TrySetResultAsync(true); + } + + //Voice States + internal SocketVoiceState AddOrUpdateVoiceState(ClientState state, VoiceStateModel model) + { + var voiceChannel = state.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; + var voiceState = SocketVoiceState.Create(voiceChannel, model); + _voiceStates[model.UserId] = voiceState; + return voiceState; + } + internal SocketVoiceState? GetVoiceState(ulong id) + { + SocketVoiceState voiceState; + if (_voiceStates.TryGetValue(id, out voiceState)) + return voiceState; + return null; + } + internal SocketVoiceState? RemoveVoiceState(ulong id) + { + SocketVoiceState voiceState; + if (_voiceStates.TryRemove(id, out voiceState)) + return voiceState; + return null; + } + + //Audio + public async Task DisconnectAudioAsync(AudioClient client = null) + { + await _audioLock.WaitAsync().ConfigureAwait(false); + try + { + await DisconnectAudioInternalAsync(client).ConfigureAwait(false); + } + finally + { + _audioLock.Release(); + } + } + private async Task DisconnectAudioInternalAsync(AudioClient client = null) + { + var oldClient = AudioClient; + if (oldClient != null) + { + if (client == null || oldClient == client) + { + _audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection + _audioConnectPromise = null; + } + if (oldClient == client) + { + AudioClient = null; + await oldClient.DisconnectAsync().ConfigureAwait(false); + } + } + } + internal async Task FinishConnectAudio(int id, string url, string token) + { + var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value; + + await _audioLock.WaitAsync().ConfigureAwait(false); + try + { + if (AudioClient == null) + { + var audioClient = new AudioClient(this, id); + audioClient.Disconnected += async ex => + { + await _audioLock.WaitAsync().ConfigureAwait(false); + try + { + if (AudioClient == audioClient) //Only reconnect if we're still assigned as this guild's audio client + { + if (ex != null) + { + //Reconnect if we still have channel info. + //TODO: Is this threadsafe? Could channel data be deleted before we access it? + var voiceState2 = GetVoiceState(Discord.CurrentUser.Id); + if (voiceState2.HasValue) + { + var voiceChannelId = voiceState2.Value.VoiceChannel?.Id; + if (voiceChannelId != null) + await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted); + } + } + else + { + try { AudioClient.Dispose(); } catch { } + AudioClient = null; + } + } + } + finally + { + _audioLock.Release(); + } + }; + AudioClient = audioClient; + } + await AudioClient.ConnectAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); + await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + await DisconnectAudioAsync(); + } + catch (Exception e) + { + await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false); + await DisconnectAudioAsync(); + } + finally + { + _audioLock.Release(); + } + } + internal async Task FinishJoinAudioChannel() + { + await _audioLock.WaitAsync().ConfigureAwait(false); + try + { + if (AudioClient != null) + await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false); + } + finally + { + _audioLock.Release(); + } + } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; + internal SocketGuild Clone() => MemberwiseClone() as SocketGuild; + //IGuild bool IGuild.Available => true; IAudioClient IGuild.AudioClient => null; - IReadOnlyCollection IGuild.CachedUsers => ImmutableArray.Create(); IRole IGuild.EveryoneRole => EveryoneRole; IReadOnlyCollection IGuild.Roles => Roles; async Task> IGuild.GetBansAsync() => await GetBansAsync(); - async Task> IGuild.GetChannelsAsync() - => await GetChannelsAsync(); - async Task IGuild.GetChannelAsync(ulong id) - => await GetChannelAsync(id); - IGuildChannel IGuild.GetCachedChannel(ulong id) - => null; + Task> IGuild.GetChannelsAsync(CacheMode mode) + => Task.FromResult>(Channels); + Task IGuild.GetChannelAsync(ulong id, CacheMode mode) + => Task.FromResult(GetChannel(id)); async Task IGuild.CreateTextChannelAsync(string name) => await CreateTextChannelAsync(name); async Task IGuild.CreateVoiceChannelAsync(string name) @@ -209,15 +553,15 @@ namespace Discord.WebSocket IRole IGuild.GetRole(ulong id) => GetRole(id); + async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted) + => await CreateRoleAsync(name, permissions, color, isHoisted); - async Task> IGuild.GetUsersAsync() - => await GetUsersAsync(); - async Task IGuild.GetUserAsync(ulong id) - => await GetUserAsync(id); - IGuildUser IGuild.GetCachedUser(ulong id) - => null; - async Task IGuild.GetCurrentUserAsync() - => await GetCurrentUserAsync(); + Task> IGuild.GetUsersAsync(CacheMode mode) + => Task.FromResult>(Users); + Task IGuild.GetUserAsync(ulong id, CacheMode mode) + => Task.FromResult(GetUser(id)); + Task IGuild.GetCurrentUserAsync(CacheMode mode) + => Task.FromResult(GetCurrentUser()); Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index d283f46b0..72e2b50ae 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -1,11 +1,8 @@ -using Discord.Rest; -using Discord.WebSocket; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Threading.Tasks; namespace Discord.WebSocket { @@ -51,7 +48,7 @@ namespace Discord.WebSocket return result; return null; } - public IImmutableList 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; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 46271c9e6..7d97ee916 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -2,13 +2,12 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Threading.Tasks; using Model = Discord.API.Message; namespace Discord.WebSocket { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public abstract class SocketMessage : SocketEntity, IMessage, IUpdateable + public abstract class SocketMessage : SocketEntity, IMessage { private long _timestampTicks; @@ -35,14 +34,14 @@ namespace Discord.WebSocket ChannelId = channelId; Author = author; } - internal static SocketMessage Create(DiscordSocketClient discord, SocketUser author, Model model) + internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) { if (model.Type == MessageType.Default) - return SocketUserMessage.Create(discord, author, model); + return SocketUserMessage.Create(discord, state, author, model); else - return SocketSystemMessage.Create(discord, author, model); + return SocketSystemMessage.Create(discord, state, author, model); } - internal virtual void Update(Model model) + internal virtual void Update(ClientState state, Model model) { if (model.Timestamp.IsSpecified) _timestampTicks = model.Timestamp.Value.UtcTicks; @@ -50,12 +49,8 @@ namespace Discord.WebSocket if (model.Content.IsSpecified) Content = model.Content.Value; } - - public async Task UpdateAsync() - { - var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id).ConfigureAwait(false); - Update(model); - } + + internal SocketMessage Clone() => MemberwiseClone() as SocketMessage; //IMessage IUser IMessage.Author => Author; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs index 56a8bafaa..61bc9af1c 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs @@ -1,5 +1,4 @@ -using Discord.Rest; -using Model = Discord.API.Message; +using Model = Discord.API.Message; namespace Discord.WebSocket { @@ -11,17 +10,21 @@ namespace Discord.WebSocket : base(discord, id, channelId, author) { } - internal new static SocketSystemMessage Create(DiscordSocketClient discord, SocketUser author, Model model) + internal new static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) { var entity = new SocketSystemMessage(discord, model.Id, model.ChannelId, author); - entity.Update(model); + entity.Update(state, model); return entity; } - internal override void Update(Model model) + internal override void Update(ClientState state, Model model) { - base.Update(model); + base.Update(state, model); Type = model.Type; } + + public override string ToString() => Content; + 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 1f60ac8f2..003acecb2 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -8,7 +8,7 @@ using Model = Discord.API.Message; namespace Discord.WebSocket { - internal class SocketUserMessage : SocketMessage, IUserMessage + public class SocketUserMessage : SocketMessage, IUserMessage { private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; @@ -32,16 +32,16 @@ namespace Discord.WebSocket : base(discord, id, channelId, author) { } - internal new static SocketUserMessage Create(DiscordSocketClient discord, SocketUser author, Model model) + internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) { var entity = new SocketUserMessage(discord, model.Id, model.ChannelId, author); - entity.Update(model); + entity.Update(state, model); return entity; } - internal override void Update(Model model) + internal override void Update(ClientState state, Model model) { - base.Update(model); + base.Update(state, model); if (model.IsTextToSpeech.IsSpecified) _isTTS = model.IsTextToSpeech.Value; @@ -129,5 +129,9 @@ namespace Discord.WebSocket text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); return text; } + + public override string ToString() => Content; + 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 new file mode 100644 index 000000000..1cb2e2812 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -0,0 +1,58 @@ +using Discord.API.Rest; +using Discord.Rest; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Role; + +namespace Discord.WebSocket +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketRole : SocketEntity, IRole + { + public SocketGuild Guild { get; } + + public Color Color { get; private set; } + public bool IsHoisted { get; private set; } + public bool IsManaged { get; private set; } + public string Name { get; private set; } + public GuildPermissions Permissions { get; private set; } + public int Position { get; private set; } + + public bool IsEveryone => Id == Guild.Id; + public string Mention => MentionUtils.MentionRole(Id); + + internal SocketRole(SocketGuild guild, ulong id) + : base(guild.Discord, id) + { + 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; + IsHoisted = model.Hoist; + IsManaged = model.Managed; + Position = model.Position; + Color = new Color(model.Color); + Permissions = new GuildPermissions(model.Permissions); + } + + public Task ModifyAsync(Action func) + => RoleHelper.ModifyAsync(this, Discord, func); + public Task DeleteAsync() + => RoleHelper.DeleteAsync(this, Discord); + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; + internal SocketRole Clone() => MemberwiseClone() as SocketRole; + + //IRole + IGuild IRole.Guild => Guild; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 39340745c..d96993887 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -1,10 +1,56 @@ -namespace Discord.WebSocket +using Model = Discord.API.User; +using System.Collections.Concurrent; + +namespace Discord.WebSocket { internal class SocketGlobalUser : SocketUser { - internal SocketGlobalUser(DiscordSocketClient discord, ulong id) + public override bool IsBot { get; internal set; } + public override string Username { get; internal set; } + public override ushort DiscriminatorValue { get; internal set; } + public override string AvatarId { get; internal set; } + public SocketDMChannel DMChannel { get; internal set; } + + internal override SocketGlobalUser GlobalUser => this; + internal override SocketPresence Presence { get; set; } + + 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); + entity.Update(state, model); + return entity; + } + + internal void AddRef() + { + checked + { + lock (_lockObj) + _references++; + } + } + internal void RemoveRef(DiscordSocketClient discord) + { + lock (_lockObj) + { + if (--_references <= 0) + discord.RemoveUser(Id); + } + } + + internal new SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser; + + //Updates are only ever called from the gateway thread, thus threadsafe + internal override void Update(ClientState state, Model model) + { + base.Update(state, model); + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index 2c2bfe74a..694d0ccb9 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -1,5 +1,4 @@ -using Discord.Rest; -using System.Diagnostics; +using System.Diagnostics; using Model = Discord.API.User; namespace Discord.WebSocket @@ -7,17 +6,30 @@ namespace Discord.WebSocket [DebuggerDisplay("{DebuggerDisplay,nq}")] public class SocketGroupUser : SocketUser, IGroupUser { - internal SocketGroupUser(DiscordSocketClient discord, ulong id) - : base(discord, id) + 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 SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser globalUser) + : base(channel.Discord, globalUser.Id) { + Channel = channel; + GlobalUser = globalUser; } - internal new static SocketGroupUser Create(DiscordSocketClient discord, Model model) + internal static SocketGroupUser Create(SocketGroupChannel channel, ClientState state, Model model) { - var entity = new SocketGroupUser(discord, model.Id); - entity.Update(model); + var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model)); + entity.Update(state, model); return entity; } + internal new SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; + //IVoiceState bool IVoiceState.IsDeafened => false; bool IVoiceState.IsMuted => false; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 33aea831c..f73b10a2e 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -9,46 +9,76 @@ using PresenceModel = Discord.API.Presence; namespace Discord.WebSocket { - internal class SocketGuildUser : SocketUser, IGuildUser + public class SocketGuildUser : SocketUser, IGuildUser { private long? _joinedAtTicks; private ImmutableArray _roleIds; + internal override SocketGlobalUser GlobalUser { get; } + public SocketGuild Guild { get; } public string Nickname { get; private set; } - public ulong GuildId { 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; } } + internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } public IReadOnlyCollection RoleIds => _roleIds; + public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); + public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; + public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; + public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; + public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; + public bool IsDeafened => VoiceState?.IsDeafened ?? false; + public bool IsMuted => VoiceState?.IsMuted ?? false; + public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; + public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); - internal SocketGuildUser(DiscordSocketClient discord, ulong id) - : base(discord, id) + internal SocketGuildUser(SocketGuild guild, SocketGlobalUser globalUser) + : base(guild.Discord, globalUser.Id) + { + Guild = guild; + GlobalUser = globalUser; + } + internal static SocketGuildUser Create(SocketGuild guild, ClientState state, Model model) { + var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); + entity.Update(state, model); + return entity; } - internal static SocketGuildUser Create(DiscordSocketClient discord, Model model) + internal static SocketGuildUser Create(SocketGuild guild, ClientState state, PresenceModel model) { - var entity = new SocketGuildUser(discord, model.User.Id); - entity.Update(model); + var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); + entity.Update(state, model); return entity; } - internal void Update(Model model) + internal void Update(ClientState state, Model model) { + base.Update(state, model.User); _joinedAtTicks = model.JoinedAt.UtcTicks; if (model.Nick.IsSpecified) Nickname = model.Nick.Value; UpdateRoles(model.Roles); } + internal override void Update(ClientState state, PresenceModel model) + { + base.Update(state, model); + if (model.Roles.IsSpecified) + UpdateRoles(model.Roles.Value); + if (model.Nick.IsSpecified) + Nickname = model.Nick.Value; + } private void UpdateRoles(ulong[] roleIds) { var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1); - roles.Add(GuildId); + roles.Add(Guild.Id); for (int i = 0; i < roleIds.Length; i++) roles.Add(roleIds[i]); _roleIds = roles.ToImmutable(); } - - public override async Task UpdateAsync() - => Update(await UserHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) => UserHelper.ModifyAsync(this, Discord, func); public Task KickAsync() @@ -59,16 +89,17 @@ namespace Discord.WebSocket throw new NotImplementedException(); //TODO: Impl } + internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; + //IGuildUser + ulong IGuildUser.GuildId => Guild.Id; IReadOnlyCollection IGuildUser.RoleIds => RoleIds; + //IUser + Task IUser.GetDMChannelAsync(CacheMode mode) + => Task.FromResult(GlobalUser.DMChannel); + //IVoiceState - bool IVoiceState.IsDeafened => false; - bool IVoiceState.IsMuted => false; - bool IVoiceState.IsSelfDeafened => false; - bool IVoiceState.IsSelfMuted => false; - bool IVoiceState.IsSuppressed => false; - IVoiceChannel IVoiceState.VoiceChannel => null; - string IVoiceState.VoiceSessionId => null; + 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 01fd85345..edfe67e3f 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -3,7 +3,7 @@ namespace Discord.WebSocket { //TODO: C#7 Candidate for record type - internal struct SocketPresence : IPresence + public struct SocketPresence : IPresence { public Game? Game { get; } public UserStatus Status { get; } @@ -13,11 +13,11 @@ namespace Discord.WebSocket Game = game; Status = status; } - internal SocketPresence Create(Model model) + internal static SocketPresence Create(Model model) { return new SocketPresence(model.Game != null ? Discord.Game.Create(model.Game) : (Game?)null, model.Status); } - public SocketPresence Clone() => this; + 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 ccbbd4850..e859d7c0a 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -11,21 +11,28 @@ namespace Discord.WebSocket public string Email { get; private set; } public bool IsVerified { get; private set; } public bool IsMfaEnabled { get; private set; } + internal override SocketGlobalUser GlobalUser { get; } - internal SocketSelfUser(DiscordSocketClient discord, ulong id) - : base(discord, id) + 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 SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) + : base(discord, globalUser.Id) { - Status = UserStatus.Online; + GlobalUser = globalUser; } - internal new static SocketSelfUser Create(DiscordSocketClient discord, Model model) + internal static SocketSelfUser Create(DiscordSocketClient discord, ClientState state, Model model) { - var entity = new SocketSelfUser(discord, model.Id); - entity.Update(model); + var entity = new SocketSelfUser(discord, discord.GetOrCreateSelfUser(state, model)); + entity.Update(state, model); return entity; } - internal override void Update(Model model) + internal override void Update(ClientState state, Model model) { - base.Update(model); + base.Update(state, model); if (model.Email.IsSpecified) Email = model.Email.Value; @@ -34,12 +41,13 @@ namespace Discord.WebSocket if (model.MfaEnabled.IsSpecified) IsMfaEnabled = model.MfaEnabled.Value; } - - public override async Task UpdateAsync() - => Update(await UserHelper.GetAsync(this, Discord)); + public Task ModifyAsync(Action func) => UserHelper.ModifyAsync(this, Discord, func); + internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; + + //ISelfUser Task ISelfUser.ModifyStatusAsync(Action func) { throw new NotSupportedException(); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs new file mode 100644 index 000000000..be2b279fc --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs @@ -0,0 +1,34 @@ +using System; +using Model = Discord.API.User; +using PresenceModel = Discord.API.Presence; + +namespace Discord.WebSocket +{ + public class SocketSimpleUser : SocketUser + { + public override bool IsBot { get; internal set; } + public override string Username { get; internal set; } + public override ushort DiscriminatorValue { get; internal set; } + public override string AvatarId { get; internal set; } + internal override SocketPresence Presence { get { return new SocketPresence(null, UserStatus.Offline); } set { } } + + internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } + + internal SocketSimpleUser(DiscordSocketClient discord, ulong id) + : base(discord, id) + { + } + internal static SocketSimpleUser Create(DiscordSocketClient discord, ClientState state, Model model) + { + var entity = new SocketSimpleUser(discord, model.Id); + entity.Update(state, model); + return entity; + } + + internal override void Update(ClientState state, PresenceModel model) + { + } + + internal new SocketSimpleUser Clone() => MemberwiseClone() as SocketSimpleUser; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 5ab68d556..a9c419cf2 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -1,33 +1,30 @@ using Discord.Rest; using System.Threading.Tasks; using Model = Discord.API.User; +using PresenceModel = Discord.API.Presence; namespace Discord.WebSocket { - public class SocketUser : SocketEntity, IUser + public abstract class SocketUser : SocketEntity, IUser { - public bool IsBot { get; private set; } - public string Username { get; private set; } - public ushort DiscriminatorValue { get; private set; } - public string AvatarId { get; private set; } + 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; } + internal abstract SocketGlobalUser GlobalUser { get; } + internal abstract SocketPresence Presence { get; set; } public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); public string Discriminator => DiscriminatorValue.ToString("D4"); public string Mention => MentionUtils.MentionUser(Id); - public virtual Game? Game => null; - public virtual UserStatus Status { get; internal set; } + public Game? Game => Presence.Game; + public UserStatus Status => Presence.Status; internal SocketUser(DiscordSocketClient discord, ulong id) : base(discord, id) { } - internal static SocketUser Create(DiscordSocketClient discord, Model model) - { - var entity = new SocketUser(discord, model.Id); - entity.Update(model); - return entity; - } - internal virtual void Update(Model model) + internal virtual void Update(ClientState state, Model model) { if (model.Avatar.IsSpecified) AvatarId = model.Avatar.Value; @@ -38,13 +35,22 @@ namespace Discord.WebSocket if (model.Username.IsSpecified) Username = model.Username.Value; } + internal virtual void Update(ClientState state, PresenceModel model) + { + Presence = SocketPresence.Create(model); + } - public virtual async Task UpdateAsync() - => Update(await UserHelper.GetAsync(this, Discord)); - - public Task CreateDMChannelAsync() + public Task CreateDMChannelAsync() => UserHelper.CreateDMChannelAsync(this, Discord); - IDMChannel IUser.GetCachedDMChannel() => null; + public override string ToString() => $"{Username}#{Discriminator}"; + private string DebuggerDisplay => $"{Username}#{Discriminator} (Id{(IsBot ? ", Bot" : "")})"; + internal SocketUser Clone() => MemberwiseClone() as SocketUser; + + //IUser + Task IUser.GetDMChannelAsync(CacheMode mode) + => Task.FromResult(GlobalUser.DMChannel); + async Task IUser.CreateDMChannelAsync() + => await CreateDMChannelAsync(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs index 9813fb039..39d8e3ae3 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs @@ -47,7 +47,7 @@ namespace Discord.WebSocket return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress); } - public SocketVoiceState Clone() => this; + internal SocketVoiceState Clone() => this; IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; } From 8aa1050b9333fb8dbc68619d30f6331986446d84 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 09:01:57 -0300 Subject: [PATCH 006/102] Cleaned up usings --- src/Discord.Net.Commands/Results/TypeReaderResult.cs | 1 - src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 6 +++--- src/Discord.Net.Core/Entities/Roles/Color.cs | 2 +- src/Discord.Net.Core/Entities/Users/IGroupUser.cs | 4 +--- src/Discord.Net.Core/Entities/Users/IGuildUser.cs | 4 ++-- .../Net/Converters/DiscordContractResolver.cs | 1 - .../Net/Queue/Requests/MultipartRestRequest.cs | 1 - src/Discord.Net.Core/Utils/MentionsHelper.cs | 1 - src/Discord.Net.Rest/BaseDiscordClient.cs | 6 +++--- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 6 +++--- src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 6 +++--- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 8 ++++---- src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs | 5 ++--- src/Discord.Net.Rest/Entities/Users/RestUser.cs | 3 +-- src/Discord.Net.Rest/Entities/Users/UserHelper.cs | 4 ++-- src/Discord.Net.Rest/Properties/AssemblyInfo.cs | 1 - src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs | 1 - src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs | 6 +----- src/Discord.Net.Rpc/Properties/AssemblyInfo.cs | 1 - src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs | 4 ++-- .../API/Gateway/VoiceServerUpdateEvent.cs | 2 +- .../Entities/Channels/SocketDMChannel.cs | 1 - .../Entities/Channels/SocketTextChannel.cs | 1 - .../Entities/Users/SocketGlobalUser.cs | 1 - src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs | 1 - src/Discord.Net/Properties/AssemblyInfo.cs | 1 - 26 files changed, 29 insertions(+), 49 deletions(-) diff --git a/src/Discord.Net.Commands/Results/TypeReaderResult.cs b/src/Discord.Net.Commands/Results/TypeReaderResult.cs index 20a9c4a22..68bc359c6 100644 --- a/src/Discord.Net.Commands/Results/TypeReaderResult.cs +++ b/src/Discord.Net.Commands/Results/TypeReaderResult.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; namespace Discord.Commands { diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 414a7cc74..f1b1da6a1 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -1,8 +1,8 @@ -using System; +using Discord.API.Rest; +using Discord.Audio; +using System; using System.Collections.Generic; using System.Threading.Tasks; -using Discord.API.Rest; -using Discord.Audio; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 0eb562e80..563917959 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics; -namespace Discord +namespace Discord { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Color diff --git a/src/Discord.Net.Core/Entities/Users/IGroupUser.cs b/src/Discord.Net.Core/Entities/Users/IGroupUser.cs index 8b4c3bf70..3d18b7cd2 100644 --- a/src/Discord.Net.Core/Entities/Users/IGroupUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGroupUser.cs @@ -1,6 +1,4 @@ -using System.Threading.Tasks; - -namespace Discord +namespace Discord { public interface IGroupUser : IUser, IVoiceState { diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index b109e2f31..768951c79 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -1,7 +1,7 @@ -using System; +using Discord.API.Rest; +using System; using System.Collections.Generic; using System.Threading.Tasks; -using Discord.API.Rest; namespace Discord { diff --git a/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs index c387db0bf..413fd512f 100644 --- a/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; diff --git a/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs index f30969e43..047e5ed02 100644 --- a/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs @@ -1,5 +1,4 @@ using Discord.Net.Rest; -using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; diff --git a/src/Discord.Net.Core/Utils/MentionsHelper.cs b/src/Discord.Net.Core/Utils/MentionsHelper.cs index 19c0fd511..3c2812595 100644 --- a/src/Discord.Net.Core/Utils/MentionsHelper.cs +++ b/src/Discord.Net.Core/Utils/MentionsHelper.cs @@ -3,7 +3,6 @@ using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Discord { diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index b6ea634d7..98d25cba3 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -1,10 +1,10 @@ -using System; +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.Logging; -using System.Collections.Immutable; namespace Discord.Rest { diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index af8ab3fc0..0022c79d9 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.IO; -using System.Threading.Tasks; using System; using System.Collections.Generic; -using System.Linq; using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.Rest diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index a00f6243f..d3fdf01d3 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -1,12 +1,12 @@ using Discord.API.Rest; using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; -using Model = Discord.API.Guild; using EmbedModel = Discord.API.GuildEmbed; +using Model = Discord.API.Guild; using RoleModel = Discord.API.Role; -using System.Linq; -using System.Collections.Immutable; namespace Discord.Rest { diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index ae61187ab..19af50ec3 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -1,12 +1,12 @@ -using System.Diagnostics; -using System.Collections.Immutable; +using Discord.API.Rest; 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.Rest; using Model = Discord.API.Guild; -using System.Linq; namespace Discord.Rest { diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 824de3c79..a588126e4 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -1,11 +1,10 @@ using Discord.API.Rest; using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Message; -using Discord.API; -using System.Collections.Generic; -using System.Collections.Immutable; namespace Discord.Rest { diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 5d704e590..b4d957f3c 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -1,5 +1,4 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index 012627283..a7ee241f5 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -1,8 +1,8 @@ using Discord.API.Rest; +using System; using System.Threading.Tasks; -using Model = Discord.API.User; using MemberModel = Discord.API.GuildMember; -using System; +using Model = Discord.API.User; namespace Discord.Rest { diff --git a/src/Discord.Net.Rest/Properties/AssemblyInfo.cs b/src/Discord.Net.Rest/Properties/AssemblyInfo.cs index f6a5354d2..bfca3dfeb 100644 --- a/src/Discord.Net.Rest/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.Rest/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs b/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs index 1b674e959..a4f42b6f5 100644 --- a/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 using Newtonsoft.Json; -using System; namespace Discord.API.Rpc { diff --git a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs index 4fbde60a9..3c0216dc9 100644 --- a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs +++ b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs @@ -1,8 +1,4 @@ -using Discord.Rest; -using System; -using Model = Discord.API.Rpc.RpcUserGuild; - -namespace Discord.Rpc +namespace Discord.Rpc { /*internal class RemoteUserGuild : RpcEntity, IRemoteUserGuild, ISnowflakeEntity { diff --git a/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs b/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs index 9cfcc4aa1..7edd100f2 100644 --- a/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs index 8b9209c61..62608b7e5 100644 --- a/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs @@ -9,11 +9,11 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; +using System.Net; +using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Net.Sockets; -using System.Net; namespace Discord.Audio { diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs index bc65ea6e8..a300f0d2c 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Gateway { - public class VoiceServerUpdateEvent + public class VoiceServerUpdateEvent { [JsonProperty("guild_id")] public ulong GuildId { get; set; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 0a747b446..314a0a3c3 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; -using MessageModel = Discord.API.Message; using Model = Discord.API.Channel; namespace Discord.WebSocket diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index c1f5ca828..6e7aeafd4 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; -using MessageModel = Discord.API.Message; using Model = Discord.API.Channel; namespace Discord.WebSocket diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index d96993887..f54fcaa73 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -1,5 +1,4 @@ using Model = Discord.API.User; -using System.Collections.Concurrent; namespace Discord.WebSocket { diff --git a/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs b/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs index 2aae053d2..86c8fc446 100644 --- a/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/Discord.Net/Properties/AssemblyInfo.cs b/src/Discord.Net/Properties/AssemblyInfo.cs index e23324a59..1dab21bf6 100644 --- a/src/Discord.Net/Properties/AssemblyInfo.cs +++ b/src/Discord.Net/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following From 708f9fe514906e619367816b1c79f097edc89416 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 09:54:44 -0300 Subject: [PATCH 007/102] Added CommandContext, fixed commands compile errors --- .../Attributes/InAttribute.cs | 8 +++ .../Attributes/PreconditionAttribute.cs | 2 +- .../Preconditions/RequireContextAttribute.cs | 2 +- .../RequirePermissionAttribute.cs | 4 +- src/Discord.Net.Commands/Command.cs | 12 ++--- src/Discord.Net.Commands/CommandContext.cs | 18 +++++++ src/Discord.Net.Commands/CommandParameter.cs | 2 +- src/Discord.Net.Commands/CommandParser.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 18 +++---- .../Readers/ChannelTypeReader.cs | 12 ++--- .../Readers/EnumTypeReader.cs | 2 +- .../Readers/MessageTypeReader.cs | 8 +-- .../Readers/RoleTypeReader.cs | 11 ++-- .../Readers/SimpleTypeReader.cs | 2 +- .../Readers/TypeReader.cs | 2 +- .../Readers/UserTypeReader.cs | 17 +++--- src/Discord.Net.Commands/project.json | 10 ++-- .../API/DiscordRestApiClient.cs | 21 -------- .../Entities/Users/IGuildUser.cs | 1 + src/Discord.Net.Core/IDiscordClient.cs | 10 ++-- src/Discord.Net.Rest/BaseDiscordClient.cs | 10 ++-- src/Discord.Net.Rest/ClientHelper.cs | 8 +-- src/Discord.Net.Rest/DiscordRestClient.cs | 52 ++++++++++++++----- .../Entities/Channels/ChannelHelper.cs | 13 +++-- .../Entities/Channels/RestGuildChannel.cs | 13 ++--- .../Entities/Channels/RestTextChannel.cs | 12 ++--- .../Entities/Channels/RestVoiceChannel.cs | 8 +-- .../Entities/Guilds/GuildHelper.cs | 12 ++--- .../Entities/Guilds/RestGuild.cs | 5 +- .../Entities/Users/RestGuildUser.cs | 27 ++++++---- .../DiscordSocketClient.cs | 10 ++-- .../Entities/Users/SocketGuildUser.cs | 1 + 32 files changed, 187 insertions(+), 148 deletions(-) create mode 100644 src/Discord.Net.Commands/Attributes/InAttribute.cs create mode 100644 src/Discord.Net.Commands/CommandContext.cs diff --git a/src/Discord.Net.Commands/Attributes/InAttribute.cs b/src/Discord.Net.Commands/Attributes/InAttribute.cs new file mode 100644 index 000000000..0d4fd9eb0 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/InAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace Discord.Commands +{ + public class InAttribute : Attribute + { + } +} diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index ae9457b92..90e82607e 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -6,6 +6,6 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class PreconditionAttribute : Attribute { - public abstract Task CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance); + public abstract Task CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index 48ada73d9..c14dbd5bf 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -21,7 +21,7 @@ namespace Discord.Commands Contexts = contexts; } - public override Task CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) + public override Task CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance) { bool isValid = false; diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs index b3215c419..3583836d8 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs @@ -20,9 +20,9 @@ namespace Discord.Commands GuildPermission = null; } - public override Task CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) + public override Task CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance) { - var guildUser = context.Author as IGuildUser; + var guildUser = context.User as IGuildUser; if (GuildPermission.HasValue) { diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index b7b7da401..2b8a46c77 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -16,7 +16,7 @@ namespace Discord.Commands private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); private readonly object _instance; - private readonly Func, Task> _action; + private readonly Func, Task> _action; public MethodInfo Source { get; } public Module Module { get; } @@ -85,7 +85,7 @@ namespace Discord.Commands } } - public async Task CheckPreconditions(IUserMessage context) + public async Task CheckPreconditions(CommandContext context) { foreach (PreconditionAttribute precondition in Module.Preconditions) { @@ -104,7 +104,7 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - public async Task Parse(IUserMessage context, SearchResult searchResult, PreconditionResult? preconditionResult = null) + public async Task Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) { if (!searchResult.IsSuccess) return ParseResult.FromError(searchResult); @@ -125,7 +125,7 @@ namespace Discord.Commands return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); } - public Task Execute(IUserMessage context, ParseResult parseResult) + public Task Execute(CommandContext context, ParseResult parseResult) { if (!parseResult.IsSuccess) return Task.FromResult(ExecuteResult.FromError(parseResult)); @@ -148,7 +148,7 @@ namespace Discord.Commands return Execute(context, argList, paramList); } - public async Task Execute(IUserMessage context, IEnumerable argList, IEnumerable paramList) + public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList) { try { @@ -209,7 +209,7 @@ namespace Discord.Commands } return paramBuilder.ToImmutable(); } - private Func, Task> BuildAction(MethodInfo methodInfo) + private Func, Task> BuildAction(MethodInfo methodInfo) { if (methodInfo.ReturnType != typeof(Task)) throw new InvalidOperationException("Commands must return a non-generic Task."); diff --git a/src/Discord.Net.Commands/CommandContext.cs b/src/Discord.Net.Commands/CommandContext.cs new file mode 100644 index 000000000..c2be3ef59 --- /dev/null +++ b/src/Discord.Net.Commands/CommandContext.cs @@ -0,0 +1,18 @@ +namespace Discord.Commands +{ + public struct CommandContext + { + public IGuild Guild { get; } + public IMessageChannel Channel { get; } + public IUser User { get; } + public IUserMessage Message { get; } + + internal CommandContext(IGuild guild, IMessageChannel channel, IUser user, IUserMessage msg) + { + Guild = guild; + Channel = channel; + User = user; + Message = msg; + } + } +} diff --git a/src/Discord.Net.Commands/CommandParameter.cs b/src/Discord.Net.Commands/CommandParameter.cs index f074876cf..1edf42bf1 100644 --- a/src/Discord.Net.Commands/CommandParameter.cs +++ b/src/Discord.Net.Commands/CommandParameter.cs @@ -32,7 +32,7 @@ namespace Discord.Commands DefaultValue = defaultValue; } - public async Task Parse(IUserMessage context, string input) + public async Task Parse(CommandContext context, string input) { return await _reader.Read(context, input).ConfigureAwait(false); } diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 77e19109a..b87e67aa0 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -13,7 +13,7 @@ namespace Discord.Commands QuotedParameter } - public static async Task ParseArgs(Command command, IUserMessage context, string input, int startPos) + public static async Task ParseArgs(Command command, CommandContext context, string input, int startPos) { CommandParameter curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 344950e68..8aff5f715 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -176,8 +176,8 @@ namespace Discord.Commands return false; } - public SearchResult Search(IUserMessage message, int argPos) => Search(message, message.Content.Substring(argPos)); - public SearchResult Search(IUserMessage message, string input) + public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); + public SearchResult Search(CommandContext context, string input) { string lowerInput = input.ToLowerInvariant(); var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray(); @@ -188,18 +188,18 @@ namespace Discord.Commands return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } - public Task Execute(IUserMessage message, int argPos, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) - => Execute(message, message.Content.Substring(argPos), multiMatchHandling); - public async Task Execute(IUserMessage message, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task Execute(CommandContext context, int argPos, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + => Execute(context, context.Message.Content.Substring(argPos), multiMatchHandling); + public async Task Execute(CommandContext context, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { - var searchResult = Search(message, input); + var searchResult = Search(context, input); if (!searchResult.IsSuccess) return searchResult; var commands = searchResult.Commands; for (int i = commands.Count - 1; i >= 0; i--) { - var preconditionResult = await commands[i].CheckPreconditions(message); + var preconditionResult = await commands[i].CheckPreconditions(context); if (!preconditionResult.IsSuccess) { if (commands.Count == 1) @@ -208,7 +208,7 @@ namespace Discord.Commands continue; } - var parseResult = await commands[i].Parse(message, searchResult, preconditionResult); + var parseResult = await commands[i].Parse(context, searchResult, preconditionResult); if (!parseResult.IsSuccess) { if (parseResult.Error == CommandError.MultipleMatches) @@ -233,7 +233,7 @@ namespace Discord.Commands } } - return await commands[i].Execute(message, parseResult); + return await commands[i].Execute(context, parseResult); } return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index 463a9de4f..8ace89961 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -9,23 +9,21 @@ namespace Discord.Commands internal class ChannelTypeReader : TypeReader where T : class, IChannel { - public override async Task Read(IUserMessage context, string input) + public override async Task Read(CommandContext context, string input) { - var guild = (context.Channel as IGuildChannel)?.Guild; - - if (guild != null) + if (context.Guild != null) { var results = new Dictionary(); - var channels = await guild.GetChannelsAsync().ConfigureAwait(false); + var channels = await context.Guild.GetChannelsAsync().ConfigureAwait(false); ulong id; //By Mention (1.0) if (MentionUtils.TryParseChannel(input, out id)) - AddResult(results, await guild.GetChannelAsync(id).ConfigureAwait(false) as T, 1.00f); + AddResult(results, await context.Guild.GetChannelAsync(id).ConfigureAwait(false) as T, 1.00f); //By Id (0.9) if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - AddResult(results, await guild.GetChannelAsync(id).ConfigureAwait(false) as T, 0.90f); + AddResult(results, await context.Guild.GetChannelAsync(id).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))) diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index 54efa8024..dca845704 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -42,7 +42,7 @@ namespace Discord.Commands _enumsByValue = byValueBuilder.ToImmutable(); } - public override Task Read(IUserMessage context, string input) + public override Task Read(CommandContext context, string input) { T baseValue; object enumValue; diff --git a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs index b509fc025..57bfc21cd 100644 --- a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs @@ -6,19 +6,19 @@ namespace Discord.Commands internal class MessageTypeReader : TypeReader where T : class, IMessage { - public override Task Read(IUserMessage context, string input) + public override async Task Read(CommandContext context, string input) { ulong id; //By Id (1.0) if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) { - var msg = context.Channel.GetCachedMessage(id) as T; + var msg = await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; if (msg != null) - return Task.FromResult(TypeReaderResult.FromSuccess(msg)); + return TypeReaderResult.FromSuccess(msg); } - return Task.FromResult(TypeReaderResult.FromError(CommandError.ObjectNotFound, "Message not found.")); + return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Message not found."); } } } diff --git a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index b386aba3c..66b76b7e7 100644 --- a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs @@ -9,23 +9,22 @@ namespace Discord.Commands internal class RoleTypeReader : TypeReader where T : class, IRole { - public override Task Read(IUserMessage context, string input) + public override Task Read(CommandContext context, string input) { - var guild = (context.Channel as IGuildChannel)?.Guild; ulong id; - if (guild != null) + if (context.Guild != null) { var results = new Dictionary(); - var roles = guild.Roles; + var roles = context.Guild.Roles; //By Mention (1.0) if (MentionUtils.TryParseRole(input, out id)) - AddResult(results, guild.GetRole(id) as T, 1.00f); + 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, guild.GetRole(id) as T, 0.90f); + 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))) diff --git a/src/Discord.Net.Commands/Readers/SimpleTypeReader.cs b/src/Discord.Net.Commands/Readers/SimpleTypeReader.cs index 72c729a3b..ad939e59d 100644 --- a/src/Discord.Net.Commands/Readers/SimpleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/SimpleTypeReader.cs @@ -11,7 +11,7 @@ namespace Discord.Commands _tryParse = PrimitiveParsers.Get(); } - public override Task Read(IUserMessage context, string input) + public override Task Read(CommandContext context, string input) { T value; if (_tryParse(input, out value)) diff --git a/src/Discord.Net.Commands/Readers/TypeReader.cs b/src/Discord.Net.Commands/Readers/TypeReader.cs index 4d467ce55..23562cb16 100644 --- a/src/Discord.Net.Commands/Readers/TypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TypeReader.cs @@ -4,6 +4,6 @@ namespace Discord.Commands { public abstract class TypeReader { - public abstract Task Read(IUserMessage context, string input); + public abstract Task Read(CommandContext context, string input); } } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 46aaa777c..c6416895a 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -9,22 +9,21 @@ namespace Discord.Commands internal class UserTypeReader : TypeReader where T : class, IUser { - public override async Task Read(IUserMessage context, string input) + public override async Task Read(CommandContext context, string input) { var results = new Dictionary(); - var guild = (context.Channel as IGuildChannel)?.Guild; - IReadOnlyCollection channelUsers = await context.Channel.GetUsersAsync().ConfigureAwait(false); + IReadOnlyCollection channelUsers = (await context.Channel.GetUsersAsync().Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way? IReadOnlyCollection guildUsers = null; ulong id; - if (guild != null) - guildUsers = await guild.GetUsersAsync().ConfigureAwait(false); + if (context.Guild != null) + guildUsers = await context.Guild.GetUsersAsync().ConfigureAwait(false); //By Mention (1.0) if (MentionUtils.TryParseUser(input, out id)) { - if (guild != null) - AddResult(results, await guild.GetUserAsync(id).ConfigureAwait(false) as T, 1.00f); + if (context.Guild != null) + AddResult(results, await context.Guild.GetUserAsync(id).ConfigureAwait(false) as T, 1.00f); else AddResult(results, await context.Channel.GetUserAsync(id).ConfigureAwait(false) as T, 1.00f); } @@ -32,8 +31,8 @@ namespace Discord.Commands //By Id (0.9) if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) { - if (guild != null) - AddResult(results, await guild.GetUserAsync(id).ConfigureAwait(false) as T, 0.90f); + if (context.Guild != null) + AddResult(results, await context.Guild.GetUserAsync(id).ConfigureAwait(false) as T, 0.90f); else AddResult(results, await context.Channel.GetUserAsync(id).ConfigureAwait(false) as T, 0.90f); } diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index c0449bf35..8e5d861d8 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -13,18 +13,14 @@ } }, - "buildOptions": { - "allowUnsafe": true, - "warningsAsErrors": false, - "xmlDoc": true - }, - "configurations": { "Release": { "buildOptions": { "define": [ "RELEASE" ], "nowarn": [ "CS1573", "CS1591" ], - "optimize": true + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true } } }, diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index 5c0aee850..b70ea015b 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -856,27 +856,6 @@ namespace Discord.API } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } } - public async Task GetUserAsync(string username, string discriminator, RequestOptions options = null) - { - Preconditions.NotNullOrEmpty(username, nameof(username)); - Preconditions.NotNullOrEmpty(discriminator, nameof(discriminator)); - options = RequestOptions.CreateOrClone(options); - - try - { - var models = await QueryUsersAsync($"{username}#{discriminator}", 1, options: options).ConfigureAwait(false); - return models.FirstOrDefault(); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } - } - public async Task> QueryUsersAsync(string query, int limit, RequestOptions options = null) - { - Preconditions.NotNullOrEmpty(query, nameof(query)); - Preconditions.AtLeast(limit, 0, nameof(limit)); - options = RequestOptions.CreateOrClone(options); - - return await SendAsync>("GET", $"users?q={Uri.EscapeDataString(query)}&limit={limit}", options: options).ConfigureAwait(false); - } //Current User/DMs public async Task GetMyUserAsync(RequestOptions options = null) diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 768951c79..72cbabb30 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -12,6 +12,7 @@ namespace Discord DateTimeOffset? JoinedAt { get; } /// Gets the nickname for this user. string Nickname { get; } + GuildPermissions GuildPermissions { get; } /// Gets the id of the guild for this user. ulong GuildId { get; } diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index 79ca30634..92dc6b7ba 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -19,18 +19,18 @@ namespace Discord Task GetApplicationInfoAsync(); - Task GetChannelAsync(ulong id); - Task> GetPrivateChannelsAsync(); + Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); + Task> GetPrivateChannelsAsync(CacheMode mode = CacheMode.AllowDownload); Task> GetConnectionsAsync(); - Task GetGuildAsync(ulong id); - Task> GetGuildsAsync(); + Task GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); + Task> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload); Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null); Task GetInviteAsync(string inviteId); - Task GetUserAsync(ulong id); + Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); Task GetUserAsync(string username, string discriminator); Task> GetVoiceRegionsAsync(); diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 98d25cba3..62dcfd055 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -129,9 +129,9 @@ namespace Discord.Rest Task IDiscordClient.GetApplicationInfoAsync() { throw new NotSupportedException(); } - Task IDiscordClient.GetChannelAsync(ulong id) + Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode) => Task.FromResult(null); - Task> IDiscordClient.GetPrivateChannelsAsync() + Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode) => Task.FromResult>(ImmutableArray.Create()); Task> IDiscordClient.GetConnectionsAsync() @@ -140,13 +140,13 @@ namespace Discord.Rest Task IDiscordClient.GetInviteAsync(string inviteId) => Task.FromResult(null); - Task IDiscordClient.GetGuildAsync(ulong id) + Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode) => Task.FromResult(null); - Task> IDiscordClient.GetGuildsAsync() + Task> IDiscordClient.GetGuildsAsync(CacheMode mode) => Task.FromResult>(ImmutableArray.Create()); Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) { throw new NotSupportedException(); } - Task IDiscordClient.GetUserAsync(ulong id) + Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); Task IDiscordClient.GetUserAsync(string username, string discriminator) => Task.FromResult(null); diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 02ccb2c5b..f1c619e02 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -94,12 +94,12 @@ namespace Discord.Rest return RestUser.Create(client, model); return null; } - public static async Task GetUserAsync(BaseDiscordClient client, - string username, string discriminator) + public static async Task GetGuildUserAsync(BaseDiscordClient client, + ulong guildId, ulong id) { - var model = await client.ApiClient.GetUserAsync(username, discriminator).ConfigureAwait(false); + var model = await client.ApiClient.GetGuildMemberAsync(guildId, id).ConfigureAwait(false); if (model != null) - return RestUser.Create(client, model); + return RestGuildUser.Create(client, new RestGuild(client, guildId), model); return null; } diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 6fa954116..2e9ff861c 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -1,5 +1,6 @@ using Discord.Net.Queue; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Threading.Tasks; @@ -60,8 +61,8 @@ namespace Discord.Rest public Task GetUserAsync(ulong id) => ClientHelper.GetUserAsync(this, id); /// - public Task GetUserAsync(string username, string discriminator) - => ClientHelper.GetUserAsync(this, username, discriminator); + public Task GetGuildUserAsync(ulong guildId, ulong id) + => ClientHelper.GetGuildUserAsync(this, guildId, id); /// public Task> GetVoiceRegionsAsync() @@ -74,10 +75,20 @@ namespace Discord.Rest async Task IDiscordClient.GetApplicationInfoAsync() => await GetApplicationInfoAsync().ConfigureAwait(false); - async Task IDiscordClient.GetChannelAsync(ulong id) - => await GetChannelAsync(id); - async Task> IDiscordClient.GetPrivateChannelsAsync() - => await GetPrivateChannelsAsync(); + async Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetChannelAsync(id); + else + return null; + } + async Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetPrivateChannelsAsync(); + else + return ImmutableArray.Create(); + } async Task> IDiscordClient.GetConnectionsAsync() => await GetConnectionsAsync().ConfigureAwait(false); @@ -85,17 +96,30 @@ namespace Discord.Rest async Task IDiscordClient.GetInviteAsync(string inviteId) => await GetInviteAsync(inviteId).ConfigureAwait(false); - async Task IDiscordClient.GetGuildAsync(ulong id) - => await GetGuildAsync(id).ConfigureAwait(false); - async Task> IDiscordClient.GetGuildsAsync() - => await GetGuildsAsync().ConfigureAwait(false); + async Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetGuildAsync(id).ConfigureAwait(false); + else + return null; + } + async Task> IDiscordClient.GetGuildsAsync(CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetGuildsAsync().ConfigureAwait(false); + else + return ImmutableArray.Create(); + } async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); - async Task IDiscordClient.GetUserAsync(ulong id) - => await GetUserAsync(id).ConfigureAwait(false); - async Task IDiscordClient.GetUserAsync(string username, string discriminator) - => await GetUserAsync(username, discriminator).ConfigureAwait(false); + async Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return await GetUserAsync(id).ConfigureAwait(false); + else + return null; + } async Task> IDiscordClient.GetVoiceRegionsAsync() => await GetVoiceRegionsAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 0022c79d9..5a14c7ee4 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -163,19 +163,19 @@ namespace Discord.Rest } //Users - public static async Task GetUserAsync(IGuildChannel channel, BaseDiscordClient client, + public static async Task GetUserAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, ulong id) { var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id); if (model == null) return null; - var user = RestGuildUser.Create(client, model); + var user = RestGuildUser.Create(client, guild, model); if (!user.GetPermissions(channel).ReadMessages) return null; return user; } - public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, BaseDiscordClient client, + public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch) { return new PagedAsyncEnumerable( @@ -188,8 +188,11 @@ namespace Discord.Rest }; if (info.Position != null) args.AfterUserId = info.Position.Value; - var models = await client.ApiClient.GetGuildMembersAsync(channel.GuildId, args); - return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); ; + var models = await guild.Discord.ApiClient.GetGuildMembersAsync(guild.Id, args); + return models + .Select(x => RestGuildUser.Create(client, guild, x)) + .Where(x => x.GetPermissions(channel).ReadMessages) + .ToImmutableArray(); }, nextPage: (info, lastPage) => { diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 7d7547713..4c0b92403 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -16,24 +16,25 @@ namespace Discord.Rest public IReadOnlyCollection PermissionOverwrites => _overwrites; - public ulong GuildId { get; } + internal IGuild Guild { get; } + public ulong GuildId => Guild.Id; public string Name { get; private set; } public int Position { get; private set; } - internal RestGuildChannel(BaseDiscordClient discord, ulong id, ulong guildId) + internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, id) { - GuildId = guildId; + Guild = guild; } - internal new static RestGuildChannel Create(BaseDiscordClient discord, Model model) + internal static RestGuildChannel Create(BaseDiscordClient discord, IGuild guild, Model model) { switch (model.Type) { case ChannelType.Text: - return RestTextChannel.Create(discord, model); + return RestTextChannel.Create(discord, guild, model); case ChannelType.Voice: - return RestVoiceChannel.Create(discord, model); + return RestVoiceChannel.Create(discord, guild, model); default: throw new InvalidOperationException("Unknown guild channel type"); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 3e660b53b..3dccc2e2b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -17,13 +17,13 @@ namespace Discord.Rest public string Mention => MentionUtils.MentionChannel(Id); - internal RestTextChannel(BaseDiscordClient discord, ulong id, ulong guildId) - : base(discord, id, guildId) + internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, guild, id) { } - internal new static RestTextChannel Create(BaseDiscordClient discord, Model model) + internal new static RestTextChannel Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestTextChannel(discord, model.Id, model.GuildId.Value); + var entity = new RestTextChannel(discord, guild, model.Id); entity.Update(model); return entity; } @@ -39,9 +39,9 @@ namespace Discord.Rest => ChannelHelper.ModifyAsync(this, Discord, func); public Task GetUserAsync(ulong id) - => ChannelHelper.GetUserAsync(this, Discord, id); + => ChannelHelper.GetUserAsync(this, Guild, Discord, id); public IAsyncEnumerable> GetUsersAsync() - => ChannelHelper.GetUsersAsync(this, Discord); + => ChannelHelper.GetUsersAsync(this, Guild, Discord); public Task GetMessageAsync(ulong id) => ChannelHelper.GetMessageAsync(this, Discord, id); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 1ff7c3075..3f59c4b0e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -16,13 +16,13 @@ namespace Discord.Rest public int Bitrate { get; private set; } public int UserLimit { get; private set; } - internal RestVoiceChannel(BaseDiscordClient discord, ulong id, ulong guildId) - : base(discord, id, guildId) + internal RestVoiceChannel(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, guild, id) { } - internal new static RestVoiceChannel Create(BaseDiscordClient discord, Model model) + internal new static RestVoiceChannel Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestVoiceChannel(discord, model.Id, model.GuildId.Value); + var entity = new RestVoiceChannel(discord, guild, model.Id); entity.Update(model); return entity; } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index d3fdf01d3..f3ab36258 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -81,13 +81,13 @@ namespace Discord.Rest { var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false); if (model != null) - return RestGuildChannel.Create(client, model); + return RestGuildChannel.Create(client, guild, model); return null; } public static async Task> GetChannelsAsync(IGuild guild, BaseDiscordClient client) { var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); - return models.Select(x => RestGuildChannel.Create(client, x)).ToImmutableArray(); + return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray(); } public static async Task CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, string name) @@ -96,7 +96,7 @@ namespace Discord.Rest var args = new CreateGuildChannelParams(name, ChannelType.Text); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); - return RestTextChannel.Create(client, model); + return RestTextChannel.Create(client, guild, model); } public static async Task CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, string name) @@ -105,7 +105,7 @@ namespace Discord.Rest var args = new CreateGuildChannelParams(name, ChannelType.Voice); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); - return RestVoiceChannel.Create(client, model); + return RestVoiceChannel.Create(client, guild, model); } //Integrations @@ -155,7 +155,7 @@ namespace Discord.Rest { var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false); if (model != null) - return RestGuildUser.Create(client, model); + return RestGuildUser.Create(client, guild, model); return null; } public static async Task GetCurrentUserAsync(IGuild guild, BaseDiscordClient client) @@ -166,7 +166,7 @@ namespace Discord.Rest { var args = new GetGuildMembersParams(); var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false); - return models.Select(x => RestGuildUser.Create(client, x)).ToImmutableArray(); + return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray(); } public static async Task PruneUsersAsync(IGuild guild, BaseDiscordClient client, int days = 30, bool simulate = false) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 19af50ec3..4c2407968 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -30,6 +30,7 @@ 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 ulong DefaultChannelId => Id; public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); @@ -87,6 +88,8 @@ namespace Discord.Rest roles[model.Roles[i].Id] = RestRole.Create(Discord, model.Roles[i]); } _roles = roles.ToImmutable(); + + Available = true; } //General @@ -169,7 +172,7 @@ namespace Discord.Rest => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); //IGuild - bool IGuild.Available => true; + bool IGuild.Available => Available; IAudioClient IGuild.AudioClient => null; IRole IGuild.EveryoneRole => EveryoneRole; IReadOnlyCollection IGuild.Roles => Roles; diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 2fe25b416..dd6056f41 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -15,19 +15,30 @@ namespace Discord.Rest private ImmutableArray _roleIds; public string Nickname { get; private set; } - public ulong GuildId { get; private set; } + internal IGuild Guild { 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."); + return new GuildPermissions(Permissions.ResolveGuild(Guild, this)); + } + } public IReadOnlyCollection RoleIds => _roleIds; public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); - internal RestGuildUser(BaseDiscordClient discord, ulong id) + internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, id) { + Guild = guild; } - internal static RestGuildUser Create(BaseDiscordClient discord, Model model) + internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestGuildUser(discord, model.User.Id); + var entity = new RestGuildUser(discord, guild, model.User.Id); entity.Update(model); return entity; } @@ -41,7 +52,7 @@ namespace Discord.Rest private void UpdateRoles(ulong[] roleIds) { var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1); - roles.Add(GuildId); + roles.Add(Guild.Id); for (int i = 0; i < roleIds.Length; i++) roles.Add(roleIds[i]); _roleIds = roles.ToImmutable(); @@ -56,12 +67,10 @@ namespace Discord.Rest public ChannelPermissions GetPermissions(IGuildChannel channel) { - throw new NotImplementedException(); //TODO: Impl + var guildPerms = GuildPermissions; + return new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, guildPerms.RawValue)); } - //IGuildUser - IReadOnlyCollection IGuildUser.RoleIds => RoleIds; - //IVoiceState bool IVoiceState.IsDeafened => false; bool IVoiceState.IsMuted => false; diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 03831818a..a6576160b 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1612,9 +1612,9 @@ namespace Discord.WebSocket async Task IDiscordClient.GetApplicationInfoAsync() => await GetApplicationInfoAsync().ConfigureAwait(false); - Task IDiscordClient.GetChannelAsync(ulong id) + Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode) => Task.FromResult(GetChannel(id)); - Task> IDiscordClient.GetPrivateChannelsAsync() + Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode) => Task.FromResult>(PrivateChannels); async Task> IDiscordClient.GetConnectionsAsync() @@ -1623,14 +1623,14 @@ namespace Discord.WebSocket async Task IDiscordClient.GetInviteAsync(string inviteId) => await GetInviteAsync(inviteId); - Task IDiscordClient.GetGuildAsync(ulong id) + Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode) => Task.FromResult(GetGuild(id)); - Task> IDiscordClient.GetGuildsAsync() + Task> IDiscordClient.GetGuildsAsync(CacheMode mode) => Task.FromResult>(Guilds); async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) => await CreateGuildAsync(name, region, jpegIcon); - Task IDiscordClient.GetUserAsync(ulong id) + Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(GetUser(id)); Task IDiscordClient.GetUserAsync(string username, string discriminator) => Task.FromResult(GetUser(username, discriminator)); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index f73b10a2e..01d473a00 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -23,6 +23,7 @@ namespace Discord.WebSocket 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 GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); public IReadOnlyCollection RoleIds => _roleIds; public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); From 9cbadd73f52f50ffe1de32ec0cf449fda02ed248 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 10:20:22 -0300 Subject: [PATCH 008/102] Included remaining projects in main package --- src/Discord.Net/project.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 6bd67ae75..f7cb1aa13 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -22,13 +22,13 @@ }, "Discord.Net.WebSocket": { "target": "project" + }, + "Discord.Net.Rpc": { + "target": "project" + }, + "Discord.Net.Commands": { + "target": "project" } - //"Discord.Net.Rpc": { - // "target": "project" - //}, - //"Discord.Net.Commands": { - // "target": "project" - //} }, "frameworks": { From cba90ebec35c7c37d5129e24a8e1d100bb7eb1b4 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 10:20:35 -0300 Subject: [PATCH 009/102] Exposed public CommandContext constructor --- src/Discord.Net.Commands/CommandContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/CommandContext.cs b/src/Discord.Net.Commands/CommandContext.cs index c2be3ef59..555141801 100644 --- a/src/Discord.Net.Commands/CommandContext.cs +++ b/src/Discord.Net.Commands/CommandContext.cs @@ -7,7 +7,7 @@ public IUser User { get; } public IUserMessage Message { get; } - internal CommandContext(IGuild guild, IMessageChannel channel, IUser user, IUserMessage msg) + public CommandContext(IGuild guild, IMessageChannel channel, IUser user, IUserMessage msg) { Guild = guild; Channel = channel; From b33a7205c1b014b631a410584bfc49b475269afd Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 10:20:51 -0300 Subject: [PATCH 010/102] Fixed null audio lock --- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 45c1a588d..3ac802434 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -78,6 +78,7 @@ namespace Discord.WebSocket internal SocketGuild(DiscordSocketClient client, ulong id) : base(client, id) { + _audioLock = new SemaphoreSlim(1, 1); _emojis = ImmutableArray.Create(); _features = ImmutableArray.Create(); } From d443ccca134c2215d21b3a159f219ef3b6c47090 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 10:21:02 -0300 Subject: [PATCH 011/102] Exposed SocketMessage.Channel --- .../Entities/Messages/SocketMessage.cs | 16 +++++++++------- .../Entities/Messages/SocketSystemMessage.cs | 8 ++++---- .../Entities/Messages/SocketUserMessage.cs | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 7d97ee916..3c169d0dd 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -10,9 +10,9 @@ namespace Discord.WebSocket public abstract class SocketMessage : SocketEntity, IMessage { private long _timestampTicks; - - public ulong ChannelId { get; } + public SocketUser Author { get; } + public ISocketMessageChannel Channel { get; } public string Content { get; private set; } @@ -28,18 +28,18 @@ namespace Discord.WebSocket public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal SocketMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author) + internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) : base(discord, id) { - ChannelId = channelId; + Channel = channel; Author = author; } - internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) + 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, model); + return SocketUserMessage.Create(discord, state, author, channel, model); else - return SocketSystemMessage.Create(discord, state, author, model); + return SocketSystemMessage.Create(discord, state, author, channel, model); } internal virtual void Update(ClientState state, Model model) { @@ -55,5 +55,7 @@ namespace Discord.WebSocket //IMessage IUser IMessage.Author => Author; MessageType IMessage.Type => MessageType.Default; + + ulong IMessage.ChannelId => Channel.Id; } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs index 61bc9af1c..87d9a19c6 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs @@ -6,13 +6,13 @@ namespace Discord.WebSocket { public MessageType Type { get; private set; } - internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author) - : base(discord, id, channelId, author) + internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) + : base(discord, id, channel, author) { } - internal new static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) + internal new static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) { - var entity = new SocketSystemMessage(discord, model.Id, model.ChannelId, author); + var entity = new SocketSystemMessage(discord, model.Id, channel, author); entity.Update(state, model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 003acecb2..9f389ad9a 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -28,13 +28,13 @@ namespace Discord.WebSocket public override IReadOnlyCollection MentionedRoles => _mentionedRoles; public override IReadOnlyCollection MentionedUsers => _mentionedUsers; - internal SocketUserMessage(DiscordSocketClient discord, ulong id, ulong channelId, SocketUser author) - : base(discord, id, channelId, author) + internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) + : base(discord, id, channel, author) { } - internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Model model) + internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) { - var entity = new SocketUserMessage(discord, model.Id, model.ChannelId, author); + var entity = new SocketUserMessage(discord, model.Id, channel, author); entity.Update(state, model); return entity; } From 0a09b0deb5d514e7af8932d129dfd088e5840462 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 10:21:41 -0300 Subject: [PATCH 012/102] Fixed errors --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index a6576160b..c75835e84 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1159,7 +1159,7 @@ namespace Discord.WebSocket if (author != null) { - var msg = SocketMessage.Create(this, State, author, data); + var msg = SocketMessage.Create(this, State, author, channel, data); SocketChannelHelper.AddMessage(channel, this, msg); await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); } @@ -1210,7 +1210,7 @@ namespace Discord.WebSocket if (author == null) author = SocketSimpleUser.Create(this, State, data.Author.Value); - after = SocketMessage.Create(this, State, author, data); + after = SocketMessage.Create(this, State, author, channel, data); } if (before != null) await _messageUpdatedEvent.InvokeAsync(before, after).ConfigureAwait(false); From fc62cd52d1a47a85c2bbde9feebd9a5619cb849f Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 10:24:01 -0300 Subject: [PATCH 013/102] Fixed command parameter check --- src/Discord.Net.Commands/Command.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 2b8a46c77..b1ec3f0e1 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -169,8 +169,8 @@ namespace Discord.Commands private IReadOnlyList BuildParameters(MethodInfo methodInfo) { var parameters = methodInfo.GetParameters(); - if (parameters.Length == 0 || parameters[0].ParameterType != typeof(IUserMessage)) - throw new InvalidOperationException($"The first parameter of a command must be {nameof(IUserMessage)}."); + if (parameters.Length == 0 || parameters[0].ParameterType != typeof(CommandContext)) + throw new InvalidOperationException($"The first parameter of a command must be {nameof(CommandContext)}."); var paramBuilder = ImmutableArray.CreateBuilder(parameters.Length - 1); for (int i = 1; i < parameters.Length; i++) From 1f03be7650186b7ebea72aaa417079d3d4c53dc5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 10:24:16 -0300 Subject: [PATCH 014/102] Fixed Add/RemoveMessage null errors --- .../Entities/Channels/SocketChannelHelper.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 856acdc73..6f59f48c1 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -58,9 +58,9 @@ namespace Discord.WebSocket if (channel is SocketDMChannel) (channel as SocketDMChannel).AddMessage(msg); else if (channel is SocketGroupChannel) - (channel as SocketDMChannel).AddMessage(msg); + (channel as SocketGroupChannel).AddMessage(msg); else if (channel is SocketTextChannel) - (channel as SocketDMChannel).AddMessage(msg); + (channel as SocketTextChannel).AddMessage(msg); else throw new NotSupportedException("Unexpected ISocketMessageChannel type"); } @@ -71,9 +71,9 @@ namespace Discord.WebSocket if (channel is SocketDMChannel) return (channel as SocketDMChannel).RemoveMessage(id); else if (channel is SocketGroupChannel) - return (channel as SocketDMChannel).RemoveMessage(id); + return (channel as SocketGroupChannel).RemoveMessage(id); else if (channel is SocketTextChannel) - return (channel as SocketDMChannel).RemoveMessage(id); + return (channel as SocketTextChannel).RemoveMessage(id); else throw new NotSupportedException("Unexpected ISocketMessageChannel type"); } From d32b701f00cb52845f46e09125dc6fd10e9fed07 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 13:36:20 -0300 Subject: [PATCH 015/102] Removed unneeded AssemblyInfo.cs files --- .../Properties/AssemblyInfo.cs | 18 ------------------ src/Discord.Net.Rpc/Properties/AssemblyInfo.cs | 18 ------------------ .../Properties/AssemblyInfo.cs | 18 ------------------ src/Discord.Net/Properties/AssemblyInfo.cs | 18 ------------------ 4 files changed, 72 deletions(-) delete mode 100644 src/Discord.Net.Rest/Properties/AssemblyInfo.cs delete mode 100644 src/Discord.Net.Rpc/Properties/AssemblyInfo.cs delete mode 100644 src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs delete mode 100644 src/Discord.Net/Properties/AssemblyInfo.cs diff --git a/src/Discord.Net.Rest/Properties/AssemblyInfo.cs b/src/Discord.Net.Rest/Properties/AssemblyInfo.cs deleted file mode 100644 index bfca3dfeb..000000000 --- a/src/Discord.Net.Rest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Discord.Net.Rest")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("bfc6dc28-0351-4573-926a-d4124244c04f")] diff --git a/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs b/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs deleted file mode 100644 index 7edd100f2..000000000 --- a/src/Discord.Net.Rpc/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Discord.Net.Rpc")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("5688a353-121e-40a1-8bfa-b17b91fb48fb")] diff --git a/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs b/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs deleted file mode 100644 index 86c8fc446..000000000 --- a/src/Discord.Net.WebSocket/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Discord.Net.WebSocket")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d")] diff --git a/src/Discord.Net/Properties/AssemblyInfo.cs b/src/Discord.Net/Properties/AssemblyInfo.cs deleted file mode 100644 index 1dab21bf6..000000000 --- a/src/Discord.Net/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Discord.Net")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("496db20a-a455-4d01-b6bc-90fe6d7c6b81")] From 857255abde47a5e16e4b41cdd950756dd38fe316 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 13:36:51 -0300 Subject: [PATCH 016/102] Optimized dependencies --- src/Discord.Net.Core/project.json | 21 +++++---------------- src/Discord.Net.Rest/project.json | 2 +- src/Discord.Net.Rpc/project.json | 3 ++- src/Discord.Net.WebSocket/project.json | 6 +++++- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Discord.Net.Core/project.json b/src/Discord.Net.Core/project.json index c7b95dcae..69d3fccb7 100644 --- a/src/Discord.Net.Core/project.json +++ b/src/Discord.Net.Core/project.json @@ -13,12 +13,6 @@ } }, - "buildOptions": { - "compile": { - "include": [ "../Discord.Net.Utils/**.cs" ] - } - }, - "configurations": { "Release": { "buildOptions": { @@ -33,20 +27,15 @@ "dependencies": { "Microsoft.Win32.Primitives": "4.0.1", - "Newtonsoft.Json": "8.0.3", + "Newtonsoft.Json": "9.0.1", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Interactive.Async": "3.0.0", - "System.IO.Compression": "4.1.0", - "System.IO.FileSystem": "4.0.1", "System.Net.Http": "4.1.0", - "System.Net.NameResolution": "4.0.0", - "System.Net.Sockets": "4.1.0", - "System.Net.WebSockets.Client": "4.0.0", - "System.Reflection.Extensions": "4.0.1", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", - "System.Runtime.Serialization.Primitives": "4.1.1", + "System.Net.WebSockets.Client": { + "version": "4.0.0", + "type": "build" + }, "System.Text.RegularExpressions": "4.1.0" }, diff --git a/src/Discord.Net.Rest/project.json b/src/Discord.Net.Rest/project.json index ac02cf639..8024001b9 100644 --- a/src/Discord.Net.Rest/project.json +++ b/src/Discord.Net.Rest/project.json @@ -17,7 +17,7 @@ "Discord.Net.Core": { "target": "project" }, - "NETStandard.Library": "1.6.0" + "System.IO.FileSystem": "4.0.1" }, "frameworks": { diff --git a/src/Discord.Net.Rpc/project.json b/src/Discord.Net.Rpc/project.json index 0d2860743..549bcc347 100644 --- a/src/Discord.Net.Rpc/project.json +++ b/src/Discord.Net.Rpc/project.json @@ -20,7 +20,8 @@ "Discord.Net.Rest": { "target": "project" }, - "NETStandard.Library": "1.6.0" + "System.IO.Compression": "4.1.0", + "System.Net.WebSockets.Client": "4.0.0" }, "frameworks": { diff --git a/src/Discord.Net.WebSocket/project.json b/src/Discord.Net.WebSocket/project.json index 7abff080a..a37f4f29b 100644 --- a/src/Discord.Net.WebSocket/project.json +++ b/src/Discord.Net.WebSocket/project.json @@ -24,7 +24,11 @@ "Discord.Net.Rest": { "target": "project" }, - "NETStandard.Library": "1.6.0" + "System.IO.Compression": "4.1.0", + "System.Net.NameResolution": "4.0.0", + "System.Net.Sockets": "4.1.0", + "System.Net.WebSockets.Client": "4.0.0", + "System.Runtime.InteropServices": "4.1.0" }, "frameworks": { From c67bd04c5a69226e778286ad2af378ba7c5ac23f Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 13:37:40 -0300 Subject: [PATCH 017/102] Fixed websocket message mention detection --- src/Discord.Net.Core/Utils/MentionsHelper.cs | 3 +++ .../Entities/Messages/SocketUserMessage.cs | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Utils/MentionsHelper.cs b/src/Discord.Net.Core/Utils/MentionsHelper.cs index 3c2812595..269e235aa 100644 --- a/src/Discord.Net.Core/Utils/MentionsHelper.cs +++ b/src/Discord.Net.Core/Utils/MentionsHelper.cs @@ -62,6 +62,9 @@ namespace Discord internal static ImmutableArray GetRoleMentions(string text, IGuild guild) where TRole : class, IRole { + if (guild == null) + return ImmutableArray.Create(); + var matches = _roleRegex.Matches(text); var builder = ImmutableArray.CreateBuilder(matches.Count); foreach (var match in matches.OfType()) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 9f389ad9a..a7c8059ef 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -96,10 +96,11 @@ namespace Discord.WebSocket if (model.Content.IsSpecified) { var text = model.Content.Value; + var guild = (Channel as SocketGuildChannel)?.Guild; - _mentionedUsers = MentionsHelper.GetUserMentions(text, null, mentions); - _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, null); - _mentionedRoles = MentionsHelper.GetRoleMentions(text, null); + _mentionedUsers = MentionsHelper.GetUserMentions(text, Channel, mentions); + _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, guild); + _mentionedRoles = MentionsHelper.GetRoleMentions(text, guild); model.Content = text; } } From eba9ae972d41a8a88a6c146a21bcdb693fdfa90b Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 13:37:56 -0300 Subject: [PATCH 018/102] Removed RuntimeInfo from initial log entry --- src/Discord.Net.Core/Logging/LogManager.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Discord.Net.Core/Logging/LogManager.cs b/src/Discord.Net.Core/Logging/LogManager.cs index 86b63f73f..3b7a4b960 100644 --- a/src/Discord.Net.Core/Logging/LogManager.cs +++ b/src/Discord.Net.Core/Logging/LogManager.cs @@ -74,17 +74,6 @@ namespace Discord.Logging public async Task WriteInitialLog() { await ClientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); - await ClientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false); - await ClientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false); - } - private static string ToArchString(Architecture arch) - { - switch (arch) - { - case Architecture.X64: return "x64"; - case Architecture.X86: return "x86"; - default: return arch.ToString(); - } } } } From b6ed02f88ad7cd39a7b2acc709474948276c922d Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 13:38:05 -0300 Subject: [PATCH 019/102] Moved AsyncEnumerable extension --- .../Extensions/AsyncEnumerableExtensions.cs | 14 ++++++++++++++ .../Utils/Paging/PagedEnumerator.cs | 8 -------- 2 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs diff --git a/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs b/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs new file mode 100644 index 000000000..f52edd719 --- /dev/null +++ b/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public static class AsyncEnumerableExtensions + { + public static async Task> Flatten(this IAsyncEnumerable> source) + { + return (await source.ToArray().ConfigureAwait(false)).SelectMany(x => x); + } + } +} diff --git a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs index 3f347440b..c74ce9687 100644 --- a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs +++ b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs @@ -56,12 +56,4 @@ namespace Discord public void Dispose() { Current = null; } } } - - public static class PagedAsyncEnumerable - { - public static async Task> Flatten(this IAsyncEnumerable> source) - { - return (await source.ToArray().ConfigureAwait(false)).SelectMany(x => x); - } - } } \ No newline at end of file From a93ddbaeb2d98e8a1589175f315aa376c5059683 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 14:16:18 -0300 Subject: [PATCH 020/102] Cleaned up paged enumerables --- .../Entities/Channels/IChannel.cs | 2 +- .../Entities/Channels/IRestMessageChannel.cs | 2 + .../Entities/Channels/RestChannel.cs | 3 +- .../Entities/Channels/RestDMChannel.cs | 4 +- .../Entities/Channels/RestGroupChannel.cs | 4 +- .../Entities/Channels/RestGuildChannel.cs | 4 +- .../Entities/Channels/RestTextChannel.cs | 6 +-- .../Entities/Channels/RestVoiceChannel.cs | 2 +- .../Entities/Guilds/GuildHelper.cs | 30 ++++++++++++--- .../Entities/Guilds/RestGuild.cs | 2 +- .../Channels/ISocketMessageChannel.cs | 6 +-- .../Entities/Channels/SocketChannel.cs | 2 +- .../Entities/Channels/SocketChannelHelper.cs | 37 +++++-------------- .../Entities/Channels/SocketDMChannel.cs | 26 ++++--------- .../Entities/Channels/SocketGroupChannel.cs | 26 ++++--------- .../Entities/Channels/SocketTextChannel.cs | 30 +++++---------- 16 files changed, 79 insertions(+), 107 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs index c81911a45..6302e993c 100644 --- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -8,7 +8,7 @@ namespace Discord /// Gets a collection of all users in this channel. IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload); - /// Gets a user in this channel with the provided id. + /// Gets a user in this channel with the provided id. Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index e15311f02..3d1233cef 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -19,6 +19,8 @@ namespace Discord.Rest IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of messages in this channel. IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of messages in this channel. + IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of pinned messages in this channel. new Task> GetPinnedMessagesAsync(); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index bd2584dce..ed7951de0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -50,6 +49,6 @@ namespace Discord.Rest Task IChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); //Overriden IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) - => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden + => AsyncEnumerable.Empty>(); //Overriden } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 3f399da57..aaa514075 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -99,14 +99,14 @@ namespace Discord.Rest if (mode == CacheMode.AllowDownload) return GetMessagesAsync(limit); else - return ImmutableArray.Create>().ToAsyncEnumerable(); + return AsyncEnumerable.Empty>(); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) { if (mode == CacheMode.AllowDownload) return GetMessagesAsync(fromMessageId, dir, limit); else - return ImmutableArray.Create>().ToAsyncEnumerable(); + return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 7e0806270..e4b9de950 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -105,14 +105,14 @@ namespace Discord.Rest if (mode == CacheMode.AllowDownload) return GetMessagesAsync(limit); else - return ImmutableArray.Create>().ToAsyncEnumerable(); + return AsyncEnumerable.Empty>(); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) { if (mode == CacheMode.AllowDownload) return GetMessagesAsync(fromMessageId, dir, limit); else - return ImmutableArray.Create>().ToAsyncEnumerable(); + return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 4c0b92403..ed5dc45af 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -138,13 +138,13 @@ namespace Discord.Rest => await RemovePermissionOverwriteAsync(user); IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) - => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? + => AsyncEnumerable.Empty>(); //Overriden //Overriden in Text/Voice //TODO: Does this actually override? Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? //IChannel IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) - => ImmutableArray.Create>().ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? + => AsyncEnumerable.Empty>(); //Overriden in Text/Voice //TODO: Does this actually override? Task IChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 3dccc2e2b..fdbc2ab98 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -80,7 +80,7 @@ namespace Discord.Rest if (mode == CacheMode.AllowDownload) return GetUsersAsync(); else - return ImmutableArray.Create>().ToAsyncEnumerable(); + return AsyncEnumerable.Empty>(); //Overriden } //IMessageChannel @@ -96,14 +96,14 @@ namespace Discord.Rest if (mode == CacheMode.AllowDownload) return GetMessagesAsync(limit); else - return ImmutableArray.Create>().ToAsyncEnumerable(); + return AsyncEnumerable.Empty>(); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) { if (mode == CacheMode.AllowDownload) return GetMessagesAsync(fromMessageId, dir, limit); else - return ImmutableArray.Create>().ToAsyncEnumerable(); + return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 3f59c4b0e..3c91cdb30 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -44,6 +44,6 @@ namespace Discord.Rest Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) - => ImmutableArray.Create>().ToAsyncEnumerable(); + => AsyncEnumerable.Empty>(); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index f3ab36258..3dc526550 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -162,11 +162,31 @@ namespace Discord.Rest { return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false); } - public static async Task> GetUsersAsync(IGuild guild, BaseDiscordClient client) - { - var args = new GetGuildMembersParams(); - var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args).ConfigureAwait(false); - return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray(); + public static IAsyncEnumerable> GetUsersAsync(IGuild guild, BaseDiscordClient client, + ulong? fromUserId = null, int limit = DiscordConfig.MaxMessagesPerBatch) + { + 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); + return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray(); + }, + nextPage: (info, lastPage) => + { + info.Position = lastPage.Max(x => x.Id); + if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) + info.Remaining = 0; + }, + start: fromUserId, + count: (uint)limit + ); } public static async Task PruneUsersAsync(IGuild guild, BaseDiscordClient client, int days = 30, bool simulate = false) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 4c2407968..b0f7c6fff 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -162,7 +162,7 @@ namespace Discord.Rest //Users public IAsyncEnumerable> GetUsersAsync() - => GuildHelper.GetUsersAsync(this, Discord).ToAsyncEnumerable(); + => GuildHelper.GetUsersAsync(this, Discord); public Task GetUserAsync(ulong id) => GuildHelper.GetUserAsync(this, Discord, id); public Task GetCurrentUserAsync() diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index a555754d2..4d7cd7142 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -21,11 +21,11 @@ namespace Discord.WebSocket /// Gets a message from this message channel with the given id, or null if not found. Task GetMessageAsync(ulong id); /// Gets the last N messages from this message channel. - Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); + IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of messages in this channel. - Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of messages in this channel. - Task> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of pinned messages in this channel. new Task> GetPinnedMessagesAsync(); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 07f9f5073..86a649367 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -42,6 +42,6 @@ namespace Discord.WebSocket Task IChannel.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(null); //Overridden IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) - => ImmutableArray.Create>().ToAsyncEnumerable(); //Overridden + => AsyncEnumerable.Empty>(); //Overridden } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 6f59f48c1..fa102c9a7 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -3,44 +3,25 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Threading.Tasks; namespace Discord.WebSocket { internal static class SocketChannelHelper { - public static async Task> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, - ulong? fromMessageId, Direction dir, int limit) + public static IAsyncEnumerable> PagedGetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, + ulong? fromMessageId, Direction dir, int limit, CacheMode mode) { - if (messages == null) //Cache disabled - { - var msgs = await ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit).Flatten(); - return msgs.ToImmutableArray(); - } + IReadOnlyCollection cachedMessages; + IAsyncEnumerable> result; - var cachedMessages = messages.GetMany(fromMessageId, dir, limit); - limit -= cachedMessages.Count; - if (limit == 0) - return cachedMessages; - - if (dir == Direction.Before) - fromMessageId = cachedMessages.Min(x => x.Id); + if (messages != null) //Cache enabled + cachedMessages = messages.GetMany(fromMessageId, dir, limit); else - fromMessageId = cachedMessages.Max(x => x.Id); - var downloadedMessages = await ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit).Flatten(); - return cachedMessages.Concat(downloadedMessages).ToImmutableArray(); - } - - public static IAsyncEnumerable> PagedGetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, - ulong? fromMessageId, Direction dir, int limit) - { - if (messages == null) //Cache disabled - return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit); + cachedMessages = ImmutableArray.Create(); - var cachedMessages = messages.GetMany(fromMessageId, dir, limit); - var result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>(); + result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>(); limit -= cachedMessages.Count; - if (limit == 0) + if (limit == 0 || mode == CacheMode.CacheOnly) return result; if (dir == Direction.Before) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 314a0a3c3..726f0bd29 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -51,12 +51,12 @@ namespace Discord.WebSocket msg = await ChannelHelper.GetMessageAsync(this, Discord, id); return msg; } - public Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); - public Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); - public Task> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); public Task> GetPinnedMessagesAsync() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -115,19 +115,9 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - { - if (mode == CacheMode.AllowDownload) - return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); - else - return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); - } + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - { - if (mode == CacheMode.AllowDownload) - return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); - else - return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - } + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 346f303cd..475b582af 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -74,12 +74,12 @@ namespace Discord.WebSocket msg = await ChannelHelper.GetMessageAsync(this, Discord, id); return msg; } - public Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); - public Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); - public Task> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); public Task> GetPinnedMessagesAsync() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -178,19 +178,9 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - { - if (mode == CacheMode.AllowDownload) - return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); - else - return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); - } + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - { - if (mode == CacheMode.AllowDownload) - return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); - else - return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - } + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 6e7aeafd4..24892f66c 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -57,12 +57,12 @@ namespace Discord.WebSocket msg = await ChannelHelper.GetMessageAsync(this, Discord, id); return msg; } - public Task> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); - public Task> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); - public Task> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); public Task> GetPinnedMessagesAsync() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -113,22 +113,12 @@ namespace Discord.WebSocket if (mode == CacheMode.AllowDownload) return await GetMessageAsync(id); else - throw new NotImplementedException(); + return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - { - if (mode == CacheMode.AllowDownload) - return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit); - else - return ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit); - } + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - { - if (mode == CacheMode.AllowDownload) - return SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit); - else - return ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - } + => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) @@ -140,4 +130,4 @@ namespace Discord.WebSocket IDisposable IMessageChannel.EnterTypingState() => EnterTypingState(); } -} +} \ No newline at end of file From 4c4ddb1bc33ce5ba2b5977e49211f7c0ea90b425 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 16:13:16 -0300 Subject: [PATCH 021/102] Added AssemblyInfo to Commands --- src/Discord.Net.Commands/AssemblyInfo.cs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/Discord.Net.Commands/AssemblyInfo.cs diff --git a/src/Discord.Net.Commands/AssemblyInfo.cs b/src/Discord.Net.Commands/AssemblyInfo.cs new file mode 100644 index 000000000..c6b5997b4 --- /dev/null +++ b/src/Discord.Net.Commands/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file From 85d3cd0a0bbc29ca8f79f7129b2bc75339961008 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 16:13:55 -0300 Subject: [PATCH 022/102] Renamed Test -> Tests in AssemblyInfo --- src/Discord.Net.Core/AssemblyInfo.cs | 2 +- src/Discord.Net.Rest/AssemblyInfo.cs | 2 +- src/Discord.Net.Rpc/AssemblyInfo.cs | 2 +- src/Discord.Net.WebSocket/AssemblyInfo.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/AssemblyInfo.cs b/src/Discord.Net.Core/AssemblyInfo.cs index 0c0f48a50..8563c4035 100644 --- a/src/Discord.Net.Core/AssemblyInfo.cs +++ b/src/Discord.Net.Core/AssemblyInfo.cs @@ -4,4 +4,4 @@ [assembly: InternalsVisibleTo("Discord.Net.Rpc")] [assembly: InternalsVisibleTo("Discord.Net.WebSocket")] [assembly: InternalsVisibleTo("Discord.Net.Commands")] -[assembly: InternalsVisibleTo("Discord.Net.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file diff --git a/src/Discord.Net.Rest/AssemblyInfo.cs b/src/Discord.Net.Rest/AssemblyInfo.cs index ea53dbb51..aff0626bf 100644 --- a/src/Discord.Net.Rest/AssemblyInfo.cs +++ b/src/Discord.Net.Rest/AssemblyInfo.cs @@ -3,4 +3,4 @@ [assembly: InternalsVisibleTo("Discord.Net.Rpc")] [assembly: InternalsVisibleTo("Discord.Net.WebSocket")] [assembly: InternalsVisibleTo("Discord.Net.Commands")] -[assembly: InternalsVisibleTo("Discord.Net.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file diff --git a/src/Discord.Net.Rpc/AssemblyInfo.cs b/src/Discord.Net.Rpc/AssemblyInfo.cs index 8767c83af..c6b5997b4 100644 --- a/src/Discord.Net.Rpc/AssemblyInfo.cs +++ b/src/Discord.Net.Rpc/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Discord.Net.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/AssemblyInfo.cs b/src/Discord.Net.WebSocket/AssemblyInfo.cs index 8767c83af..c6b5997b4 100644 --- a/src/Discord.Net.WebSocket/AssemblyInfo.cs +++ b/src/Discord.Net.WebSocket/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Discord.Net.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file From 1ecd8c4ad7074b0e3ebb1f01cee35dca67f3f1db Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 16:14:16 -0300 Subject: [PATCH 023/102] Fixed page logic --- src/Discord.Net.Core/Utils/Paging/PageInfo.cs | 9 ++++-- .../Utils/Paging/PagedEnumerator.cs | 28 +++++++++++++++---- .../Entities/Channels/ChannelHelper.cs | 4 +-- .../Entities/Guilds/GuildHelper.cs | 2 +- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/Discord.Net.Core/Utils/Paging/PageInfo.cs b/src/Discord.Net.Core/Utils/Paging/PageInfo.cs index cb573f3cf..3b49225f2 100644 --- a/src/Discord.Net.Core/Utils/Paging/PageInfo.cs +++ b/src/Discord.Net.Core/Utils/Paging/PageInfo.cs @@ -4,17 +4,20 @@ { public int Page { get; set; } public ulong? Position { get; set; } - public uint? Count { get; set; } + public int? Count { get; set; } public int PageSize { get; set; } - public uint? Remaining { get; set; } + public int? Remaining { get; set; } - internal PageInfo(ulong? pos, uint? count, int pageSize) + internal PageInfo(ulong? pos, int? count, int pageSize) { Page = 1; Position = pos; Count = count; Remaining = count; PageSize = pageSize; + + if (Count != null && Count.Value < PageSize) + PageSize = Count.Value; } } } diff --git a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs index c74ce9687..e4572e653 100644 --- a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs +++ b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs @@ -11,12 +11,12 @@ namespace Discord public int PageSize { get; } private readonly ulong? _start; - private readonly uint? _count; + private readonly int? _count; private readonly Func>> _getPage; private readonly Action> _nextPage; public PagedAsyncEnumerable(int pageSize, Func>> getPage, Action> nextPage = null, - ulong? start = null, uint? count = null) + ulong? start = null, int? count = null) { PageSize = pageSize; _start = start; @@ -42,15 +42,31 @@ namespace Discord public async Task MoveNext(CancellationToken cancelToken) { + if (_info.Remaining == 0) + return false; + var data = await _source._getPage(_info, cancelToken); Current = new Page(_info, data); _info.Page++; - _info.Remaining -= (uint)Current.Count; - _info.PageSize = _info.Remaining != null ? (int)Math.Min(_info.Remaining.Value, (ulong)_source.PageSize) : _source.PageSize; - _source?._nextPage(_info, data); + if (_info.Remaining != null) + { + if (Current.Count >= _info.Remaining) + _info.Remaining = 0; + else + _info.Remaining -= Current.Count; + } + else + { + 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) + _source?._nextPage(_info, data); - return _info.Remaining > 0; + return true; } public void Dispose() { Current = null; } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 5a14c7ee4..5c3f8e7d2 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -99,7 +99,7 @@ namespace Discord.Rest info.Remaining = 0; }, start: fromMessageId, - count: (uint)limit + count: limit ); } public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client) @@ -176,7 +176,7 @@ namespace Discord.Rest return user; } public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, - ulong? froUserId = null, uint? limit = DiscordConfig.MaxUsersPerBatch) + ulong? froUserId = null, int? limit = DiscordConfig.MaxUsersPerBatch) { return new PagedAsyncEnumerable( DiscordConfig.MaxUsersPerBatch, diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 3dc526550..e6aec2f70 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -185,7 +185,7 @@ namespace Discord.Rest info.Remaining = 0; }, start: fromUserId, - count: (uint)limit + count: limit ); } public static async Task PruneUsersAsync(IGuild guild, BaseDiscordClient client, From e70d4787595188693c4595b2625d37ea55353947 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 16:14:23 -0300 Subject: [PATCH 024/102] Exposed DiscordSocketClient.Guilds --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index c75835e84..2495166f7 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -55,8 +55,8 @@ namespace Discord.WebSocket public new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } } + public IReadOnlyCollection Guilds => State.Guilds; public IReadOnlyCollection PrivateChannels => State.PrivateChannels; - internal IReadOnlyCollection Guilds => State.Guilds; /// Creates a new REST/WebSocket discord client. public DiscordSocketClient() : this(new DiscordSocketConfig()) { } From 7df38fea3a74e589758cae96445df7b969e1fe51 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 16:41:40 -0300 Subject: [PATCH 025/102] Added missing ToString and DebuggerDisplays --- src/Discord.Net.Rest/Entities/Channels/RestChannel.cs | 2 -- src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs | 1 - .../Entities/Channels/RestGroupChannel.cs | 3 +++ .../Entities/Channels/RestGuildChannel.cs | 6 +++--- src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 3 ++- .../Entities/Channels/RestVoiceChannel.cs | 3 ++- src/Discord.Net.Rest/Entities/Guilds/RestBan.cs | 1 + src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 3 +++ .../Entities/Invites/RestInviteMetadata.cs | 1 - src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs | 8 ++++++-- src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs | 7 ++++++- src/Discord.Net.Rest/Entities/Messages/RestMessage.cs | 4 ++-- .../Entities/Messages/RestSystemMessage.cs | 2 ++ src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs | 2 ++ src/Discord.Net.Rest/Entities/RestApplication.cs | 6 +++++- src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 3 +++ src/Discord.Net.Rest/Entities/Users/RestConnection.cs | 2 +- src/Discord.Net.Rest/Entities/Users/RestUser.cs | 4 ++++ .../Entities/Messages/SocketMessage.cs | 4 ++-- .../Entities/Messages/SocketSystemMessage.cs | 7 ++++--- .../Entities/Messages/SocketUserMessage.cs | 3 ++- .../Entities/Users/SocketGlobalUser.cs | 4 +++- .../Entities/Users/SocketGuildUser.cs | 2 ++ .../Entities/Users/SocketPresence.cs | 7 ++++++- .../Entities/Users/SocketSelfUser.cs | 2 ++ .../Entities/Users/SocketSimpleUser.cs | 2 ++ src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs | 2 +- .../Entities/Users/SocketVoiceState.cs | 8 ++++++-- 28 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index ed7951de0..952a9e0fe 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class RestChannel : RestEntity, IChannel, IUpdateable { internal RestChannel(BaseDiscordClient discord, ulong id) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index aaa514075..e549eb87d 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -76,7 +76,6 @@ namespace Discord.Rest public override string ToString() => $"@{Recipient}"; private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; - //IDMChannel IUser IDMChannel.Recipient => Recipient; diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index e4b9de950..0ceadfa5a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -86,6 +86,9 @@ namespace Discord.Rest public IDisposable EnterTypingState() => ChannelHelper.EnterTypingState(this, Discord); + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}, Group)"; + //ISocketPrivateChannel IReadOnlyCollection IRestPrivateChannel.Recipients => Recipients; diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index ed5dc45af..bb706fd07 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -2,14 +2,12 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable { private ImmutableArray _overwrites; @@ -117,7 +115,9 @@ namespace Discord.Rest => await ChannelHelper.GetInvitesAsync(this, Discord); public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); - + + public override string ToString() => Name; + //IGuildChannel async Task> IGuildChannel.GetInvitesAsync() => await GetInvitesAsync(); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index fdbc2ab98..3511a2c3d 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -1,7 +1,6 @@ using Discord.API.Rest; using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; @@ -67,6 +66,8 @@ namespace Discord.Rest public IDisposable EnterTypingState() => ChannelHelper.EnterTypingState(this, Discord); + private string DebuggerDisplay => $"{Name} ({Id}, Text)"; + //IGuildChannel async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) { diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 3c91cdb30..02eab4776 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -2,7 +2,6 @@ using Discord.Audio; using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -37,6 +36,8 @@ namespace Discord.Rest public Task ModifyAsync(Action func) => ChannelHelper.ModifyAsync(this, Discord, func); + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; + //IVoiceChannel Task IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs index d28856d6d..104bec903 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs @@ -22,6 +22,7 @@ namespace Discord.Rest public override string ToString() => User.ToString(); private string DebuggerDisplay => $"{User}: {Reason}"; + //IBan IUser IBan.User => User; } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index b0f7c6fff..8f5d269e5 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -171,6 +171,9 @@ namespace Discord.Rest public Task PruneUsersAsync(int days = 30, bool simulate = false) => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; + //IGuild bool IGuild.Available => Available; IAudioClient IGuild.AudioClient => null; diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 131211a39..cd1a66311 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -4,7 +4,6 @@ using Model = Discord.API.InviteMetadata; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestInviteMetadata : RestInvite, IInviteMetadata { private long _createdAtTicks; diff --git a/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs b/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs index 3d9492f2e..5d31558c8 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs @@ -1,8 +1,9 @@ -using Model = Discord.API.Attachment; +using System.Diagnostics; +using Model = Discord.API.Attachment; namespace Discord { - //TODO: Rename to Attachment? + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestAttachment : IAttachment { public ulong Id { get; } @@ -29,5 +30,8 @@ namespace Discord 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)"; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs b/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs index bc7b09faf..32856ebb6 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.Embed; +using System.Diagnostics; +using Model = Discord.API.Embed; namespace Discord { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestEmbed : IEmbed { public string Description { get; } @@ -26,5 +28,8 @@ namespace Discord model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null); } + + public override string ToString() => Title; + private string DebuggerDisplay => $"{Title} ({Type})"; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 660cdadf3..d2d5d722f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Message; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class RestMessage : RestEntity, IMessage, IUpdateable { private long _timestampTicks; @@ -56,6 +54,8 @@ namespace Discord.Rest Update(model); } + public override string ToString() => Content; + MessageType IMessage.Type => MessageType.Default; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index 9cef3f479..7c62b4217 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -24,5 +24,7 @@ namespace Discord.Rest 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 a588126e4..a8cc928a5 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -130,5 +130,7 @@ namespace Discord.Rest text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); return text; } + + private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")}"; } } diff --git a/src/Discord.Net.Rest/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index e0b119381..26456c79c 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Application; namespace Discord.Rest { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestApplication : RestEntity, IApplication { protected string _iconId; @@ -27,7 +29,6 @@ namespace Discord.Rest entity.Update(model); return entity; } - internal void Update(Model model) { Description = model.Description; @@ -45,5 +46,8 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to update this object from a different application token."); Update(response); } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; } } diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 35d4a14f5..1f8dd0f67 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -46,6 +46,9 @@ namespace Discord.Rest public Task DeleteAsync() => RoleHelper.DeleteAsync(this, Discord); + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; + //IRole IGuild IRole.Guild => Guild; } diff --git a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs index a369edd96..2ee1e0e7d 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs @@ -30,6 +30,6 @@ namespace Discord } public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}, Type = {Type}{(IsRevoked ? ", Revoked" : "")})"; + private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked ? ", Revoked" : "")})"; } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index b4d957f3c..34cb9437b 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -46,6 +46,10 @@ namespace Discord.Rest public Task CreateDMChannelAsync() => UserHelper.CreateDMChannelAsync(this, Discord); + public override string ToString() => $"{Username}#{Discriminator}"; + internal string DebuggerDisplay => $"{Username}#{Discriminator} (Id{(IsBot ? ", Bot" : "")})"; + + //IUser Task IUser.GetDMChannelAsync(CacheMode mode) => Task.FromResult(null); async Task IUser.CreateDMChannelAsync() diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 3c169d0dd..f64fe811b 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -6,7 +6,6 @@ using Model = Discord.API.Message; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class SocketMessage : SocketEntity, IMessage { private long _timestampTicks; @@ -49,7 +48,8 @@ namespace Discord.WebSocket if (model.Content.IsSpecified) Content = model.Content.Value; } - + + public override string ToString() => Content; internal SocketMessage Clone() => MemberwiseClone() as SocketMessage; //IMessage diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs index 87d9a19c6..7678bb412 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.Message; +using System.Diagnostics; +using Model = Discord.API.Message; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] internal class SocketSystemMessage : SocketMessage, ISystemMessage { public MessageType Type { get; private set; } @@ -22,8 +24,7 @@ namespace Discord.WebSocket Type = model.Type; } - - public override string ToString() => Content; + 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 a7c8059ef..ec26c7dc5 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -3,11 +3,13 @@ using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Message; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketUserMessage : SocketMessage, IUserMessage { private bool _isMentioningEveryone, _isTTS, _isPinned; @@ -131,7 +133,6 @@ namespace Discord.WebSocket return text; } - public override string ToString() => Content; 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/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index f54fcaa73..f0b23543e 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.User; +using System.Diagnostics; +using Model = Discord.API.User; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] internal class SocketGlobalUser : SocketUser { public override bool IsBot { get; internal set; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 01d473a00..b99019c2c 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -3,12 +3,14 @@ using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.GuildMember; using PresenceModel = Discord.API.Presence; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGuildUser : SocketUser, IGuildUser { private long? _joinedAtTicks; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs index edfe67e3f..8dfae8d49 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -1,8 +1,10 @@ -using Model = Discord.API.Presence; +using System.Diagnostics; +using Model = Discord.API.Presence; namespace Discord.WebSocket { //TODO: C#7 Candidate for record type + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct SocketPresence : IPresence { public Game? Game { get; } @@ -18,6 +20,9 @@ namespace Discord.WebSocket return new SocketPresence(model.Game != null ? Discord.Game.Create(model.Game) : (Game?)null, model.Status); } + public override string ToString() => Status.ToString(); + internal string DebuggerDisplay => $"{Status}{(Game != null ? $", {Game.Value.Name} ({Game.Value.StreamType})" : "")}"; + 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 e859d7c0a..21578de41 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -1,11 +1,13 @@ using Discord.API.Rest; using Discord.Rest; using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketSelfUser : SocketUser, ISelfUser { public string Email { get; private set; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs index be2b279fc..a45a68703 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics; using Model = Discord.API.User; using PresenceModel = Discord.API.Presence; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketSimpleUser : SocketUser { public override bool IsBot { get; internal set; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index a9c419cf2..3f4b6426b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -44,7 +44,7 @@ namespace Discord.WebSocket => UserHelper.CreateDMChannelAsync(this, Discord); public override string ToString() => $"{Username}#{Discriminator}"; - private string DebuggerDisplay => $"{Username}#{Discriminator} (Id{(IsBot ? ", Bot" : "")})"; + internal string DebuggerDisplay => $"{Username}#{Discriminator} (Id{(IsBot ? ", Bot" : "")})"; internal SocketUser Clone() => MemberwiseClone() as SocketUser; //IUser diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs index 39d8e3ae3..ed4036362 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs @@ -1,15 +1,17 @@ using System; +using System.Diagnostics; using Model = Discord.API.VoiceState; namespace Discord.WebSocket { //TODO: C#7 Candidate for record type + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct SocketVoiceState : IVoiceState { [Flags] private enum Flags : byte { - None = 0x00, + Normal = 0x00, Suppressed = 0x01, Muted = 0x02, Deafened = 0x04, @@ -33,7 +35,7 @@ namespace Discord.WebSocket VoiceChannel = voiceChannel; VoiceSessionId = sessionId; - Flags voiceStates = Flags.None; + Flags voiceStates = Flags.Normal; if (isSelfMuted) voiceStates |= Flags.SelfMuted; if (isSelfDeafened) @@ -47,6 +49,8 @@ namespace Discord.WebSocket return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress); } + public override string ToString() => VoiceChannel?.Name ?? "Unknown"; + internal string DebuggerDisplay => $"{VoiceChannel?.Name ?? "Unknown"} ({_voiceStates})"; internal SocketVoiceState Clone() => this; IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; From 6ae508f3f7f48d67f21008cf567279e34269f3e5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 16:52:43 -0300 Subject: [PATCH 026/102] Added WEBHOOKS_UPDATE to ignore list --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 2495166f7..5ded4ea80 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1473,15 +1473,18 @@ namespace Discord.WebSocket case "CHANNEL_PINS_UPDATE": await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_UPDATE)"); break; - case "MESSAGE_ACK": - await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); - return; case "GUILD_INTEGRATIONS_UPDATE": await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); return; + case "MESSAGE_ACK": + await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); + return; case "USER_SETTINGS_UPDATE": await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); return; + case "WEBHOOKS_UPDATE": + await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); + return; //Others default: From 71c06dc2aed218aa56186e0830c6bb0cc14e8edd Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 4 Oct 2016 16:53:11 -0300 Subject: [PATCH 027/102] Typo --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 5ded4ea80..18fe2a610 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1483,7 +1483,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); return; case "WEBHOOKS_UPDATE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); return; //Others From 8be5b3697dab33698903d08477deea5e47d930a1 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 01:59:39 -0300 Subject: [PATCH 028/102] Fixed DefaultMessageNotifs enum --- .../Entities/Guilds/DefaultMessageNotifications.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs b/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs index efc107537..a5cabc117 100644 --- a/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs +++ b/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs @@ -2,9 +2,9 @@ { public enum DefaultMessageNotifications { - /// By default, only mentions will trigger notifications. - MentionsOnly = 0, /// By default, all messages will trigger notifications. - AllMessages = 1 + AllMessages = 0, + /// By default, only mentions will trigger notifications. + MentionsOnly = 1 } } From aa363fe058b4f3cfc4fb1e1347d8402dee62ca4a Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 02:04:52 -0300 Subject: [PATCH 029/102] Fixed DebuggerDisplay for users --- src/Discord.Net.Rest/Entities/Users/RestUser.cs | 2 +- src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 34cb9437b..46cd246bf 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -47,7 +47,7 @@ namespace Discord.Rest => UserHelper.CreateDMChannelAsync(this, Discord); public override string ToString() => $"{Username}#{Discriminator}"; - internal string DebuggerDisplay => $"{Username}#{Discriminator} (Id{(IsBot ? ", Bot" : "")})"; + internal string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; //IUser Task IUser.GetDMChannelAsync(CacheMode mode) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 3f4b6426b..574b508ea 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -44,7 +44,7 @@ namespace Discord.WebSocket => UserHelper.CreateDMChannelAsync(this, Discord); public override string ToString() => $"{Username}#{Discriminator}"; - internal string DebuggerDisplay => $"{Username}#{Discriminator} (Id{(IsBot ? ", Bot" : "")})"; + internal string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; internal SocketUser Clone() => MemberwiseClone() as SocketUser; //IUser From 91154ba1d66d8db0d97dd9116dc3277270854bf9 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 02:26:15 -0300 Subject: [PATCH 030/102] Defaulted concurrency level for concurrent collections --- .../Entities/Channels/SocketGroupChannel.cs | 6 +++--- src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 475b582af..710270e39 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -34,8 +34,8 @@ namespace Discord.WebSocket { if (Discord.MessageCacheSize > 0) _messages = new MessageCache(Discord, this); - _voiceStates = new ConcurrentDictionary(1, 5); - _users = new ConcurrentDictionary(1, 5); + _voiceStates = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 5); + _users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 5); } internal static SocketGroupChannel Create(DiscordSocketClient discord, ClientState state, Model model) { @@ -55,7 +55,7 @@ namespace Discord.WebSocket } internal virtual void UpdateUsers(ClientState state, UserModel[] models) { - var users = new ConcurrentDictionary(1, (int)(models.Length * 1.05)); + var users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(models.Length * 1.05)); for (int i = 0; i < models.Length; i++) users[models[i].Id] = SocketGroupUser.Create(this, state, models[i]); _users = users; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index 72e2b50ae..70110387f 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -17,7 +17,7 @@ namespace Discord.WebSocket public MessageCache(DiscordSocketClient discord, IChannel channel) { _size = discord.MessageCacheSize; - _messages = new ConcurrentDictionary(1, (int)(_size * 1.05)); + _messages = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(_size * 1.05)); _orderedMessages = new ConcurrentQueue(); } From 51a476c1fa3873681881ba2aaf455eb485209ae5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 02:58:04 -0300 Subject: [PATCH 031/102] Added GetCachedMessages --- .../Entities/Channels/IMessageChannel.cs | 2 ++ .../Entities/Channels/RestDMChannel.cs | 7 +++++++ .../Entities/Channels/RestGroupChannel.cs | 7 +++++++ .../Entities/Channels/RestTextChannel.cs | 7 +++++++ .../Entities/Channels/ISocketMessageChannel.cs | 8 +++----- .../Entities/Channels/SocketChannelHelper.cs | 12 ++++++++++-- .../Entities/Channels/SocketDMChannel.cs | 18 +++++++++++++----- .../Entities/Channels/SocketGroupChannel.cs | 18 +++++++++++++----- .../Entities/Channels/SocketTextChannel.cs | 18 +++++++++++++----- 9 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index b8333c64a..2fa09cd08 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -20,6 +20,8 @@ namespace Discord IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload); /// Gets a collection of messages in this channel. IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload); + /// Gets a collection of messages in this channel. + IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload); /// Gets a collection of pinned messages in this channel. Task> GetPinnedMessagesAsync(); /// Bulk deletes multiple messages. diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index e549eb87d..43f318abe 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -107,6 +107,13 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(fromMessage, dir, limit); + else + return AsyncEnumerable.Empty>(); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 0ceadfa5a..645ce26c8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -117,6 +117,13 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(fromMessage, dir, limit); + else + return AsyncEnumerable.Empty>(); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 3511a2c3d..77677a5bf 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -106,6 +106,13 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(fromMessage, dir, limit); + else + return AsyncEnumerable.Empty>(); + } async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index 4d7cd7142..00833717a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -18,14 +18,12 @@ namespace Discord.WebSocket new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); SocketMessage GetCachedMessage(ulong id); - /// Gets a message from this message channel with the given id, or null if not found. - Task GetMessageAsync(ulong id); /// Gets the last N messages from this message channel. - IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); + IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of messages in this channel. - IAsyncEnumerable> GetMessagesAsync(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. - IAsyncEnumerable> GetMessagesAsync(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(); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index fa102c9a7..47c290452 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -8,7 +8,7 @@ namespace Discord.WebSocket { internal static class SocketChannelHelper { - public static IAsyncEnumerable> PagedGetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, + public static IAsyncEnumerable> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, ulong? fromMessageId, Direction dir, int limit, CacheMode mode) { IReadOnlyCollection cachedMessages; @@ -21,7 +21,7 @@ namespace Discord.WebSocket result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>(); limit -= cachedMessages.Count; - if (limit == 0 || mode == CacheMode.CacheOnly) + if (mode == CacheMode.CacheOnly || limit <= 0) return result; if (dir == Direction.Before) @@ -31,6 +31,14 @@ namespace Discord.WebSocket var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit); return result.Concat(downloadedMessages); } + 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(); + } public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, SocketMessage msg) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 726f0bd29..872973627 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -52,11 +52,17 @@ namespace Discord.WebSocket return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + 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() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -115,9 +121,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode); async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 710270e39..85dab8d89 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -75,11 +75,17 @@ namespace Discord.WebSocket return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + 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() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -178,9 +184,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode); async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync(); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 24892f66c..2e894afde 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -58,11 +58,17 @@ namespace Discord.WebSocket return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + 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() => ChannelHelper.GetPinnedMessagesAsync(this, Discord); @@ -116,9 +122,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.PagedGetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode); async Task> IMessageChannel.GetPinnedMessagesAsync() => await GetPinnedMessagesAsync().ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) From 9401542666586694381d2652cd1b0f53742bb3c7 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 03:00:48 -0300 Subject: [PATCH 032/102] Updated bulk delete endpoint --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index b70ea015b..51c40eadf 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -416,7 +416,7 @@ namespace Discord.API await DeleteMessageAsync(channelId, args.MessageIds[0]).ConfigureAwait(false); break; default: - await SendJsonAsync("POST", $"channels/{channelId}/messages/bulk_delete", args, options: options).ConfigureAwait(false); + await SendJsonAsync("POST", $"channels/{channelId}/messages/bulk-delete", args, options: options).ConfigureAwait(false); break; } } From 05a04629c9695dfc7f90767c7c88907ad6e77fdb Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 03:07:54 -0300 Subject: [PATCH 033/102] Added ManageWebhooks/ManageEmojis --- .../Entities/Permissions/ChannelPermission.cs | 4 +++- .../Entities/Permissions/GuildPermission.cs | 4 +++- .../Entities/Permissions/GuildPermissions.cs | 18 +++++++++++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index 44f3aa20b..5bedfbfae 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -29,9 +29,11 @@ MoveMembers = 24, UseVAD = 25, - //Nicknames + //General2 //ChangeNickname = 26, //ManageNicknames = 27, ManagePermissions = 28, + //ManageWebhooks = 29, + //ManageEmojis = 30 } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 73a449851..e74a4da49 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -29,9 +29,11 @@ MoveMembers = 24, UseVAD = 25, - //Nicknames + //General2 ChangeNickname = 26, ManageNicknames = 27, ManageRoles = 28, + ManageWebhooks = 29, + ManageEmojis = 30 } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index a921724cf..1796944c4 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -67,6 +67,10 @@ namespace Discord 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 emojis 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; } @@ -77,7 +81,7 @@ namespace Discord bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? userExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, - bool? manageRoles = null) + bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) { ulong value = initialValue; @@ -105,6 +109,8 @@ namespace Discord Permissions.SetValue(ref value, changeNickname, GuildPermission.ChangeNickname); Permissions.SetValue(ref value, manageNicknames, GuildPermission.ManageNicknames); Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); + Permissions.SetValue(ref value, manageWebhooks, GuildPermission.ManageWebhooks); + Permissions.SetValue(ref value, manageEmojis, GuildPermission.ManageEmojis); RawValue = value; } @@ -116,10 +122,11 @@ namespace Discord bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, bool moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false, - bool manageRoles = false) + bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles) { } + speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, + manageWebhooks, manageEmojis) { } /// Creates a new GuildPermissions from this one, changing the provided non-null permissions. public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null, @@ -128,10 +135,11 @@ namespace Discord bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, - bool? manageRoles = null) + bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles); + speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, + manageWebhooks, manageEmojis); public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission); From ca3e7df0658c589651693920c32471f3bbcc277b Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 03:11:11 -0300 Subject: [PATCH 034/102] Updated Guild Permissions mask --- src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 1796944c4..5941fde97 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -11,7 +11,7 @@ namespace Discord public static readonly GuildPermissions None = new GuildPermissions(); /// Gets a GuildPermissions that grants all permissions. //TODO: C#7 Candidate for binary literals - public static readonly GuildPermissions All = new GuildPermissions(Convert.ToUInt64("00011111111100111111110000111111", 2)); + public static readonly GuildPermissions All = new GuildPermissions(Convert.ToUInt64("01111111111100111111110000111111", 2)); /// Gets a packed value representing all the permissions in this GuildPermissions. public ulong RawValue { get; } From 1b868127419828ba23de92635199ed821496bf29 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 03:43:59 -0300 Subject: [PATCH 035/102] Added WebhookId and IsWebhook --- src/Discord.Net.Core/API/Common/Message.cs | 2 ++ src/Discord.Net.Rest/Entities/Messages/RestMessage.cs | 1 + .../Entities/Messages/RestUserMessage.cs | 9 +++++++-- .../Entities/Messages/SocketMessage.cs | 2 +- .../Entities/Messages/SocketUserMessage.cs | 7 ++++++- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/API/Common/Message.cs b/src/Discord.Net.Core/API/Common/Message.cs index e22c26028..fb89f9a60 100644 --- a/src/Discord.Net.Core/API/Common/Message.cs +++ b/src/Discord.Net.Core/API/Common/Message.cs @@ -12,6 +12,8 @@ namespace Discord.API public MessageType Type { get; set; } [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + [JsonProperty("webhook_id")] + public Optional WebhookId { get; set; } [JsonProperty("author")] public Optional Author { get; set; } [JsonProperty("content")] diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index d2d5d722f..1566fb6b8 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -17,6 +17,7 @@ namespace Discord.Rest public virtual bool IsTTS => false; public virtual bool IsPinned => false; + public virtual bool IsWebhook => false; public virtual DateTimeOffset? EditedTimestamp => null; public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index a8cc928a5..fc59b0057 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -18,9 +18,12 @@ namespace Discord.Rest private ImmutableArray _mentionedChannelIds; private ImmutableArray _mentionedRoles; private ImmutableArray _mentionedUsers; - + + public ulong? WebhookId { get; private set; } + public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; + public override bool IsWebhook => WebhookId != null; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); public override IReadOnlyCollection Attachments => _attachments; @@ -52,6 +55,8 @@ namespace Discord.Rest _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; if (model.MentionEveryone.IsSpecified) _isMentioningEveryone = model.MentionEveryone.Value; + if (model.WebhookId.IsSpecified) + WebhookId = model.WebhookId.Value; if (model.Attachments.IsSpecified) { @@ -131,6 +136,6 @@ namespace Discord.Rest return text; } - private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")}"; + private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index f64fe811b..a4418a2e8 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using Model = Discord.API.Message; namespace Discord.WebSocket @@ -17,6 +16,7 @@ namespace Discord.WebSocket public virtual bool IsTTS => false; public virtual bool IsPinned => false; + public virtual bool IsWebhook => false; public virtual DateTimeOffset? EditedTimestamp => null; public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index ec26c7dc5..042248152 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -20,8 +20,11 @@ namespace Discord.WebSocket private ImmutableArray _mentionedRoles; private ImmutableArray _mentionedUsers; + public ulong? WebhookId { get; private set; } + public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; + public override bool IsWebhook => WebhookId != null; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); public override IReadOnlyCollection Attachments => _attachments; @@ -53,6 +56,8 @@ namespace Discord.WebSocket _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; if (model.MentionEveryone.IsSpecified) _isMentioningEveryone = model.MentionEveryone.Value; + if (model.WebhookId.IsSpecified) + WebhookId = model.WebhookId.Value; if (model.Attachments.IsSpecified) { @@ -133,7 +138,7 @@ namespace Discord.WebSocket return text; } - private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")}"; + private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage; } } From 3c3e790edd07d1fd89d375fa6305f7b810bfa5e0 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 5 Oct 2016 23:59:55 -0300 Subject: [PATCH 036/102] Replaced several arrays with immutables --- src/Discord.Net.Commands/CommandService.cs | 6 +++--- src/Discord.Net.Commands/Readers/UserTypeReader.cs | 3 ++- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs | 4 ++-- .../Entities/Guilds/RestGuildIntegration.cs | 1 - src/Discord.Net.Rest/Entities/Invites/RestInvite.cs | 1 - src/Discord.Net.Rest/Entities/Messages/RestMessage.cs | 1 - src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs | 1 - src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 1 - src/Discord.Net.Rest/Entities/Users/RestConnection.cs | 1 - .../API/Gateway/RequestMembersParams.cs | 4 +--- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 6 +++--- 12 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 8aff5f715..b610fa4b7 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -213,12 +213,12 @@ namespace Discord.Commands { if (parseResult.Error == CommandError.MultipleMatches) { - TypeReaderValue[] argList, paramList; + IReadOnlyList argList, paramList; switch (multiMatchHandling) { case MultiMatchHandling.Best: - argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); - paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); + 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(); parseResult = ParseResult.FromSuccess(argList, paramList); break; } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index c6416895a..c77ece4a1 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -74,7 +75,7 @@ namespace Discord.Commands } if (results.Count > 0) - return TypeReaderResult.FromSuccess(results.Values.ToArray()); + return TypeReaderResult.FromSuccess(results.Values.ToImmutableArray()); return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); } diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index 51c40eadf..d8f279132 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -832,7 +832,7 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); options = RequestOptions.CreateOrClone(options); - var roles = args.ToArray(); + var roles = args.ToImmutableArray(); switch (roles.Length) { case 0: diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index bb706fd07..72dd4a48b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -15,11 +15,11 @@ namespace Discord.Rest public IReadOnlyCollection PermissionOverwrites => _overwrites; internal IGuild Guild { get; } - public ulong GuildId => Guild.Id; - 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) { diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs index 79b75170b..b90c492ab 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs @@ -17,7 +17,6 @@ namespace Discord.Rest public bool IsSyncing { get; private set; } public ulong ExpireBehavior { get; private set; } public ulong ExpireGracePeriod { get; private set; } - public ulong GuildId { get; private set; } public ulong RoleId { get; private set; } public RestUser User { get; private set; } diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 6beabacb1..983b2986b 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -9,7 +9,6 @@ namespace Discord.Rest { public string ChannelName { get; private set; } public string GuildName { get; private set; } - public ulong ChannelId { get; private set; } public ulong GuildId { get; private set; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 1566fb6b8..bf1a1e2a6 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -19,7 +19,6 @@ namespace Discord.Rest public virtual bool IsPinned => false; public virtual bool IsWebhook => false; public virtual DateTimeOffset? EditedTimestamp => null; - public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index fc59b0057..b1da6c0b5 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -25,7 +25,6 @@ namespace Discord.Rest public override bool IsPinned => _isPinned; public override bool IsWebhook => WebhookId != null; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); - public override IReadOnlyCollection Attachments => _attachments; public override IReadOnlyCollection Embeds => _embeds; public override IReadOnlyCollection MentionedChannelIds => _mentionedChannelIds; diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 1f8dd0f67..d2c3e01bd 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -10,7 +10,6 @@ namespace Discord.Rest public class RestRole : RestEntity, IRole { public RestGuild Guild { get; } - public Color Color { get; private set; } public bool IsHoisted { get; private set; } public bool IsManaged { get; private set; } diff --git a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs index 2ee1e0e7d..b8b83be3e 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs @@ -12,7 +12,6 @@ namespace Discord 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) diff --git a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs index d32f80522..a33d1f412 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs @@ -14,8 +14,6 @@ namespace Discord.API.Gateway public int Limit { get; set; } [JsonProperty("guild_id")] - private ulong[] _guildIds { get; set; } - public IEnumerable GuildIds { set { _guildIds = value.ToArray(); } } - public IEnumerable Guilds { set { _guildIds = value.Select(x => x.Id).ToArray(); } } + private ulong[] GuildIds { get; set; } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 18fe2a610..4f3960623 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -404,11 +404,11 @@ namespace Discord.WebSocket => DownloadUsersAsync(guilds.Select(x => x as SocketGuild).Where(x => x != null)); private async Task DownloadUsersAsync(IEnumerable guilds) { - var cachedGuilds = guilds.ToArray(); + var cachedGuilds = guilds.ToImmutableArray(); if (cachedGuilds.Length == 0) return; //Wait for unsynced guilds to sync first. - var unsyncedGuilds = guilds.Select(x => x.SyncPromise).Where(x => !x.IsCompleted).ToArray(); + var unsyncedGuilds = guilds.Select(x => x.SyncPromise).Where(x => !x.IsCompleted).ToImmutableArray(); if (unsyncedGuilds.Length > 0) await Task.WhenAll(unsyncedGuilds); @@ -1565,7 +1565,7 @@ namespace Discord.WebSocket } private async Task SyncGuildsAsync() { - var guildIds = Guilds.Where(x => !x.IsSynced).Select(x => x.Id).ToArray(); + var guildIds = Guilds.Where(x => !x.IsSynced).Select(x => x.Id).ToImmutableArray(); if (guildIds.Length > 0) await ApiClient.SendGuildSyncAsync(guildIds).ConfigureAwait(false); } From 7e246f942e61a5f82c168503141e06b07c82f3b7 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 00:02:13 -0300 Subject: [PATCH 037/102] Cleaned up virtual methods --- src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs | 2 +- src/Discord.Net.WebSocket/Audio/AudioClient.cs | 2 +- .../Entities/Channels/SocketGroupChannel.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 645ce26c8..d06077a4a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -41,7 +41,7 @@ namespace Discord.Rest if (model.Recipients.IsSpecified) UpdateUsers(model.Recipients.Value); } - internal virtual void UpdateUsers(API.User[] models) + internal void UpdateUsers(API.User[] models) { var users = ImmutableDictionary.CreateBuilder(); for (int i = 0; i < models.Length; i++) diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 5bf686dc4..de20fcdff 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -300,7 +300,7 @@ namespace Discord.Audio catch (OperationCanceledException) { } } - internal virtual void Dispose(bool disposing) + internal void Dispose(bool disposing) { if (!_isDisposed) _isDisposed = true; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 85dab8d89..a096e169e 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -53,7 +53,7 @@ namespace Discord.WebSocket if (model.Recipients.IsSpecified) UpdateUsers(state, model.Recipients.Value); } - internal virtual void UpdateUsers(ClientState state, UserModel[] models) + 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++) From 2f3831dd6e1dd56f8ebe560ad492ea191f691fa9 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 00:20:26 -0300 Subject: [PATCH 038/102] Claned up mentions, added sanitize handler to user/role/channel mentions --- .../Mentions/ChannelMentionHandling.cs | 3 +- .../Messages/Mentions/RoleMentionHandling.cs | 3 +- .../Messages/Mentions/UserMentionHandling.cs | 3 +- src/Discord.Net.Core/Utils/MentionUtils.cs | 205 +++++++++++++++++- src/Discord.Net.Core/Utils/MentionsHelper.cs | 198 ----------------- .../Entities/Messages/RestUserMessage.cs | 14 +- .../API/Gateway/RequestMembersParams.cs | 3 +- .../Entities/Messages/SocketUserMessage.cs | 14 +- 8 files changed, 223 insertions(+), 220 deletions(-) delete mode 100644 src/Discord.Net.Core/Utils/MentionsHelper.cs diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs index 39f9baa6a..ea1b91688 100644 --- a/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs +++ b/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs @@ -4,6 +4,7 @@ { Ignore = 0, Remove, - Name + Name, + Sanitize } } diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs index 466cf1fd8..94d4a382f 100644 --- a/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs +++ b/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs @@ -4,6 +4,7 @@ { Ignore = 0, Remove, - Name + Name, + Sanitize } } diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs index b31a994a2..42914f393 100644 --- a/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs +++ b/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs @@ -5,6 +5,7 @@ Ignore = 0, Remove, Name, - NameAndDiscriminator + NameAndDiscriminator, + Sanitize } } diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index acfe35a03..51463cdae 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -1,14 +1,27 @@ using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; namespace Discord { public static class MentionUtils { + private const char SanitizeChar = '\x200b'; + + private static readonly Regex _userRegex = new Regex(@"<@!?([0-9]+)>", RegexOptions.Compiled); + private static readonly Regex _channelRegex = new Regex(@"<#([0-9]+)>", RegexOptions.Compiled); + private static readonly Regex _roleRegex = new Regex(@"<@&([0-9]+)>", RegexOptions.Compiled); + //If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake) - public static string MentionUser(ulong id) => MentionsHelper.MentionUser(id, true); - public static string MentionChannel(ulong id) => MentionsHelper.MentionChannel(id); - public static string MentionRole(ulong id) => MentionsHelper.MentionRole(id); + 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 MentionChannel(string id) => $"<#{id}>"; + public static string MentionChannel(ulong id) => MentionChannel(id.ToString()); + 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 mentionText) @@ -81,5 +94,191 @@ namespace Discord roleId = 0; return false; } + + internal static ImmutableArray GetUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentionedUsers) + where TUser : class, IUser + { + var matches = _userRegex.Matches(text); + var builder = ImmutableArray.CreateBuilder(matches.Count); + foreach (var match in matches.OfType()) + { + ulong id; + if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + { + TUser user = null; + + //Verify this user was actually mentioned + foreach (var userMention in mentionedUsers) + { + if (userMention.Id == id) + { + user = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult() as TUser; + if (user == null) //User not found, fallback to basic mention info + user = userMention; + break; + } + } + + if (user != null) + builder.Add(user); + } + } + return builder.ToImmutable(); + } + internal static ImmutableArray GetChannelMentions(string text, IGuild guild) + { + var matches = _channelRegex.Matches(text); + var builder = ImmutableArray.CreateBuilder(matches.Count); + foreach (var match in matches.OfType()) + { + ulong id; + if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + builder.Add(id); + } + return builder.ToImmutable(); + } + internal static ImmutableArray GetRoleMentions(string text, IGuild guild) + where TRole : class, IRole + { + if (guild == null) + return ImmutableArray.Create(); + + var matches = _roleRegex.Matches(text); + var builder = ImmutableArray.CreateBuilder(matches.Count); + foreach (var match in matches.OfType()) + { + ulong id; + if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + { + var role = guild.GetRole(id) as TRole; + if (role != null) + builder.Add(role); + } + } + return builder.ToImmutable(); + } + + internal static string ResolveUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentions, UserMentionHandling mode) + { + if (mode == UserMentionHandling.Ignore) return text; + + return _userRegex.Replace(text, new MatchEvaluator(e => + { + ulong id; + if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + { + IUser user = null; + foreach (var mention in mentions) + { + if (mention.Id == id) + { + user = mention; + break; + } + } + if (user != null) + { + string name = user.Username; + + var guildUser = user as IGuildUser; + if (e.Value[2] == '!') + { + if (guildUser != null && guildUser.Nickname != null) + name = guildUser.Nickname; + } + + switch (mode) + { + case UserMentionHandling.Name: + return $"@{name}"; + case UserMentionHandling.NameAndDiscriminator: + return $"@{name}#{user.Discriminator}"; + case UserMentionHandling.Sanitize: + return MentionUser($"{SanitizeChar}{id}"); + case UserMentionHandling.Remove: + default: + return ""; + } + } + } + return e.Value; + })); + } + internal static string ResolveChannelMentions(string text, IGuild guild, ChannelMentionHandling mode) + { + if (mode == ChannelMentionHandling.Ignore) return text; + + return _channelRegex.Replace(text, new MatchEvaluator(e => + { + ulong id; + if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + { + switch (mode) + { + case ChannelMentionHandling.Name: + IGuildChannel channel = null; + channel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); + if (channel != null) + return $"#{channel.Name}"; + else + return $"#deleted-channel"; + case ChannelMentionHandling.Sanitize: + return MentionChannel($"{SanitizeChar}{id}"); + case ChannelMentionHandling.Remove: + default: + return ""; + } + } + return e.Value; + })); + } + internal static string ResolveRoleMentions(string text, IReadOnlyCollection mentions, RoleMentionHandling mode) + { + if (mode == RoleMentionHandling.Ignore) return text; + + return _roleRegex.Replace(text, new MatchEvaluator(e => + { + ulong id; + if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + { + switch (mode) + { + case RoleMentionHandling.Name: + IRole role = null; + foreach (var mention in mentions) + { + if (mention.Id == id) + { + role = mention; + break; + } + } + if (role != null) + return $"{role.Name}"; + else + return $"deleted-role"; + case RoleMentionHandling.Sanitize: + return MentionRole($"{SanitizeChar}{id}"); + case RoleMentionHandling.Remove: + default: + return ""; + } + } + return e.Value; + })); + } + internal static string ResolveEveryoneMentions(string text, EveryoneMentionHandling mode) + { + if (mode == EveryoneMentionHandling.Ignore) return text; + + switch (mode) + { + case EveryoneMentionHandling.Sanitize: + return text.Replace("@everyone", $"@{SanitizeChar}everyone").Replace("@here", $"@{SanitizeChar}here"); + case EveryoneMentionHandling.Remove: + default: + return text.Replace("@everyone", "").Replace("@here", ""); + } + } } } diff --git a/src/Discord.Net.Core/Utils/MentionsHelper.cs b/src/Discord.Net.Core/Utils/MentionsHelper.cs deleted file mode 100644 index 269e235aa..000000000 --- a/src/Discord.Net.Core/Utils/MentionsHelper.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using System.Linq; -using System.Text.RegularExpressions; - -namespace Discord -{ - internal static class MentionsHelper - { - private static readonly Regex _userRegex = new Regex(@"<@!?([0-9]+)>", RegexOptions.Compiled); - private static readonly Regex _channelRegex = new Regex(@"<#([0-9]+)>", RegexOptions.Compiled); - private static readonly Regex _roleRegex = new Regex(@"<@&([0-9]+)>", RegexOptions.Compiled); - - //If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake) - internal static string MentionUser(ulong id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>"; - internal static string MentionChannel(ulong id) => $"<#{id}>"; - internal static string MentionRole(ulong id) => $"<@&{id}>"; - - internal static ImmutableArray GetUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentionedUsers) - where TUser : class, IUser - { - var matches = _userRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); - foreach (var match in matches.OfType()) - { - ulong id; - if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - { - TUser user = null; - - //Verify this user was actually mentioned - foreach (var userMention in mentionedUsers) - { - if (userMention.Id == id) - { - user = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult() as TUser; - if (user == null) //User not found, fallback to basic mention info - user = userMention; - break; - } - } - - if (user != null) - builder.Add(user); - } - } - return builder.ToImmutable(); - } - internal static ImmutableArray GetChannelMentions(string text, IGuild guild) - { - var matches = _channelRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); - foreach (var match in matches.OfType()) - { - ulong id; - if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - builder.Add(id); - } - return builder.ToImmutable(); - } - internal static ImmutableArray GetRoleMentions(string text, IGuild guild) - where TRole : class, IRole - { - if (guild == null) - return ImmutableArray.Create(); - - var matches = _roleRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); - foreach (var match in matches.OfType()) - { - ulong id; - if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - { - var role = guild.GetRole(id) as TRole; - if (role != null) - builder.Add(role); - } - } - return builder.ToImmutable(); - } - - internal static string ResolveUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentions, UserMentionHandling mode) - { - if (mode == UserMentionHandling.Ignore) return text; - - return _userRegex.Replace(text, new MatchEvaluator(e => - { - ulong id; - if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - { - IUser user = null; - foreach (var mention in mentions) - { - if (mention.Id == id) - { - user = mention; - break; - } - } - if (user != null) - { - string name = user.Username; - - var guildUser = user as IGuildUser; - if (e.Value[2] == '!') - { - if (guildUser != null && guildUser.Nickname != null) - name = guildUser.Nickname; - } - - switch (mode) - { - case UserMentionHandling.Remove: - default: - return ""; - case UserMentionHandling.Name: - return $"@{name}"; - case UserMentionHandling.NameAndDiscriminator: - return $"@{name}#{user.Discriminator}"; - } - } - } - return e.Value; - })); - } - internal static string ResolveChannelMentions(string text, IGuild guild, ChannelMentionHandling mode) - { - if (mode == ChannelMentionHandling.Ignore) return text; - - return _channelRegex.Replace(text, new MatchEvaluator(e => - { - ulong id; - if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - { - switch (mode) - { - case ChannelMentionHandling.Remove: - return ""; - case ChannelMentionHandling.Name: - IGuildChannel channel = null; - channel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); - if (channel != null) - return $"#{channel.Name}"; - else - return $"#deleted-channel"; - } - } - return e.Value; - })); - } - internal static string ResolveRoleMentions(string text, IReadOnlyCollection mentions, RoleMentionHandling mode) - { - if (mode == RoleMentionHandling.Ignore) return text; - - return _roleRegex.Replace(text, new MatchEvaluator(e => - { - ulong id; - if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - { - switch (mode) - { - case RoleMentionHandling.Remove: - return ""; - case RoleMentionHandling.Name: - IRole role = null; - foreach (var mention in mentions) - { - if (mention.Id == id) - { - role = mention; - break; - } - } - if (role != null) - return $"@{role.Name}"; - else - return $"@deleted-role"; - } - } - return e.Value; - })); - } - internal static string ResolveEveryoneMentions(string text, EveryoneMentionHandling mode) - { - if (mode == EveryoneMentionHandling.Ignore) return text; - - switch (mode) - { - case EveryoneMentionHandling.Sanitize: - return text.Replace("@everyone", "@\x200beveryone").Replace("@here", "@\x200bhere"); - case EveryoneMentionHandling.Remove: - default: - return text.Replace("@everyone", "").Replace("@here", ""); - } - } - } -} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index b1da6c0b5..e21295a92 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -102,9 +102,9 @@ namespace Discord.Rest { var text = model.Content.Value; - _mentionedUsers = MentionsHelper.GetUserMentions(text, null, mentions); - _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, null); - _mentionedRoles = MentionsHelper.GetRoleMentions(text, null); + _mentionedUsers = MentionUtils.GetUserMentions(text, null, mentions); + _mentionedChannelIds = MentionUtils.GetChannelMentions(text, null); + _mentionedRoles = MentionUtils.GetRoleMentions(text, null); model.Content = text; } } @@ -128,10 +128,10 @@ namespace Discord.Rest public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) { - text = MentionsHelper.ResolveUserMentions(text, null, MentionedUsers, userHandling); - text = MentionsHelper.ResolveChannelMentions(text, null, channelHandling); - text = MentionsHelper.ResolveRoleMentions(text, MentionedRoles, roleHandling); - text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); + text = MentionUtils.ResolveUserMentions(text, null, MentionedUsers, userHandling); + text = MentionUtils.ResolveChannelMentions(text, null, channelHandling); + text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling); + text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling); return text; } diff --git a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs index a33d1f412..05ec87f56 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs @@ -1,7 +1,6 @@ #pragma warning disable CS1591 using Newtonsoft.Json; using System.Collections.Generic; -using System.Linq; namespace Discord.API.Gateway { @@ -14,6 +13,6 @@ namespace Discord.API.Gateway public int Limit { get; set; } [JsonProperty("guild_id")] - private ulong[] GuildIds { get; set; } + public IEnumerable GuildIds { get; set; } } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 042248152..683f6ec3d 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -105,9 +105,9 @@ namespace Discord.WebSocket var text = model.Content.Value; var guild = (Channel as SocketGuildChannel)?.Guild; - _mentionedUsers = MentionsHelper.GetUserMentions(text, Channel, mentions); - _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, guild); - _mentionedRoles = MentionsHelper.GetRoleMentions(text, guild); + _mentionedUsers = MentionUtils.GetUserMentions(text, Channel, mentions); + _mentionedChannelIds = MentionUtils.GetChannelMentions(text, guild); + _mentionedRoles = MentionUtils.GetRoleMentions(text, guild); model.Content = text; } } @@ -131,10 +131,10 @@ namespace Discord.WebSocket public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) { - text = MentionsHelper.ResolveUserMentions(text, null, MentionedUsers, userHandling); - text = MentionsHelper.ResolveChannelMentions(text, null, channelHandling); - text = MentionsHelper.ResolveRoleMentions(text, MentionedRoles, roleHandling); - text = MentionsHelper.ResolveEveryoneMentions(text, everyoneHandling); + text = MentionUtils.ResolveUserMentions(text, null, MentionedUsers, userHandling); + text = MentionUtils.ResolveChannelMentions(text, null, channelHandling); + text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling); + text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling); return text; } From dcd94381fc9dc45b8fa4efdbac5a4d3ae9743070 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 01:04:04 -0300 Subject: [PATCH 039/102] Implemented missing method and typing notifier --- .../Entities/Channels/ChannelHelper.cs | 4 +- src/Discord.Net.Rest/Utils/TypingNotifier.cs | 44 +++++++++++++++++++ .../Entities/Users/SocketGuildUser.cs | 6 +-- 3 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 src/Discord.Net.Rest/Utils/TypingNotifier.cs diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 5c3f8e7d2..37c7ed6dd 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -207,8 +207,6 @@ namespace Discord.Rest //Typing public static IDisposable EnterTypingState(IChannel channel, BaseDiscordClient client) - { - throw new NotImplementedException(); //TODO: Impl - } + => new TypingNotifier(client, channel); } } diff --git a/src/Discord.Net.Rest/Utils/TypingNotifier.cs b/src/Discord.Net.Rest/Utils/TypingNotifier.cs new file mode 100644 index 000000000..f16e8db0f --- /dev/null +++ b/src/Discord.Net.Rest/Utils/TypingNotifier.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + internal class TypingNotifier : IDisposable + { + private readonly BaseDiscordClient _client; + private readonly CancellationTokenSource _cancelToken; + private readonly ulong _channelId; + + public TypingNotifier(BaseDiscordClient discord, IChannel channel) + { + _client = discord; + _cancelToken = new CancellationTokenSource(); + _channelId = channel.Id; + var _ = Run(); + } + + private async Task Run() + { + try + { + var token = _cancelToken.Token; + while (!_cancelToken.IsCancellationRequested) + { + try + { + await _client.ApiClient.TriggerTypingIndicatorAsync(_channelId); + } + catch { } + await Task.Delay(4500, token); + } + } + catch (OperationCanceledException) { } + } + + public void Dispose() + { + _cancelToken.Cancel(); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index b99019c2c..6573e6d0d 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -24,9 +24,9 @@ namespace Discord.WebSocket 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 GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); public IReadOnlyCollection RoleIds => _roleIds; + internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; @@ -88,9 +88,7 @@ namespace Discord.WebSocket => UserHelper.KickAsync(this, Discord); public ChannelPermissions GetPermissions(IGuildChannel channel) - { - throw new NotImplementedException(); //TODO: Impl - } + => new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, GuildPermissions.RawValue)); internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; From aa7d9ad414b350415abf6f97a5a850c294d5bc9a Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 01:09:52 -0300 Subject: [PATCH 040/102] Increased TypingNotifier delay --- src/Discord.Net.Rest/Utils/TypingNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/Utils/TypingNotifier.cs b/src/Discord.Net.Rest/Utils/TypingNotifier.cs index f16e8db0f..28da4fb75 100644 --- a/src/Discord.Net.Rest/Utils/TypingNotifier.cs +++ b/src/Discord.Net.Rest/Utils/TypingNotifier.cs @@ -30,7 +30,7 @@ namespace Discord.Rest await _client.ApiClient.TriggerTypingIndicatorAsync(_channelId); } catch { } - await Task.Delay(4500, token); + await Task.Delay(9750, token); } } catch (OperationCanceledException) { } From f41df1f96694a3780cfd4b280240acce70cf8c9f Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 02:44:41 -0300 Subject: [PATCH 041/102] Exposed RequestOptions --- .../Entities/Channels/IChannel.cs | 4 +- .../Entities/Channels/IDMChannel.cs | 2 +- .../Entities/Channels/IGroupChannel.cs | 2 +- .../Entities/Channels/IGuildChannel.cs | 18 +-- .../Entities/Channels/IMessageChannel.cs | 23 +-- .../Entities/Channels/ITextChannel.cs | 2 +- .../Entities/Channels/IVoiceChannel.cs | 2 +- .../Entities/Guilds/IGuild.cs | 44 ++--- src/Discord.Net.Core/Entities/IDeletable.cs | 2 +- src/Discord.Net.Core/Entities/IUpdateable.cs | 2 +- .../Entities/Invites/IInvite.cs | 2 +- .../Entities/Messages/IUserMessage.cs | 6 +- src/Discord.Net.Core/Entities/Roles/IRole.cs | 8 +- .../Entities/Users/IGroupUser.cs | 2 +- .../Entities/Users/IGuildUser.cs | 4 +- .../Entities/Users/ISelfUser.cs | 4 +- src/Discord.Net.Core/Entities/Users/IUser.cs | 4 +- .../Entities/Channels/ChannelHelper.cs | 97 ++++++----- .../Entities/Channels/IRestMessageChannel.cs | 16 +- .../Entities/Channels/RestChannel.cs | 6 +- .../Entities/Channels/RestDMChannel.cs | 98 ++++++------ .../Entities/Channels/RestGroupChannel.cs | 103 ++++++------ .../Entities/Channels/RestGuildChannel.cs | 71 +++++---- .../Entities/Channels/RestTextChannel.cs | 96 +++++------ .../Entities/Channels/RestVoiceChannel.cs | 8 +- .../Entities/Guilds/GuildHelper.cs | 95 ++++++----- .../Entities/Guilds/RestGuild.cs | 150 +++++++++--------- .../Entities/Guilds/RestUserGuild.cs | 8 +- .../Entities/Invites/InviteHelper.cs | 14 +- .../Entities/Invites/RestInvite.cs | 17 +- .../Entities/Messages/MessageHelper.cs | 24 +-- .../Entities/Messages/RestMessage.cs | 4 +- .../Entities/Messages/RestUserMessage.cs | 16 +- .../Entities/Roles/RestRole.cs | 8 +- .../Entities/Roles/RoleHelper.cs | 9 +- .../Entities/Users/RestGuildUser.cs | 17 +- .../Entities/Users/RestSelfUser.cs | 20 ++- .../Entities/Users/RestUser.cs | 19 ++- .../Entities/Users/UserHelper.cs | 40 ++--- src/Discord.Net.Rest/Utils/TypingNotifier.cs | 4 +- .../Channels/ISocketMessageChannel.cs | 8 +- .../Entities/Channels/SocketChannel.cs | 4 +- .../Entities/Channels/SocketChannelHelper.cs | 4 +- .../Entities/Channels/SocketDMChannel.cs | 86 +++++----- .../Entities/Channels/SocketGroupChannel.cs | 85 +++++----- .../Entities/Channels/SocketGuildChannel.cs | 64 ++++---- .../Entities/Channels/SocketTextChannel.cs | 84 +++++----- .../Entities/Channels/SocketVoiceChannel.cs | 8 +- .../Entities/Guilds/SocketGuild.cs | 111 ++++++------- .../Entities/Messages/SocketUserMessage.cs | 18 +-- .../Entities/Roles/SocketRole.cs | 8 +- .../Entities/Users/SocketGuildUser.cs | 10 +- .../Entities/Users/SocketPresence.cs | 8 +- .../Entities/Users/SocketUser.cs | 10 +- 54 files changed, 803 insertions(+), 776 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs index 6302e993c..97e58355c 100644 --- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -6,9 +6,9 @@ namespace Discord public interface IChannel : ISnowflakeEntity { /// Gets a collection of all users in this channel. - IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload); + 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); + 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 a5a3a4168..1608d1543 100644 --- a/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs @@ -8,6 +8,6 @@ namespace Discord IUser Recipient { get; } /// Closes this private channel, removing it from your channel list. - Task CloseAsync(); + 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 23f5b4784..d6cb2c182 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs @@ -5,6 +5,6 @@ namespace Discord public interface IGroupChannel : IMessageChannel, IPrivateChannel, IAudioChannel { /// Leaves this group. - Task LeaveAsync(); + 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 085771fbf..bb9f39c71 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -21,29 +21,29 @@ namespace Discord /// 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 = 1800, int? maxUses = default(int?), bool isTemporary = false); + Task CreateInviteAsync(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, RequestOptions options = null); /// Returns a collection of all invites to this channel. - Task> GetInvitesAsync(); + Task> GetInvitesAsync(RequestOptions options = null); /// Modifies this guild channel. - Task ModifyAsync(Action func); + Task ModifyAsync(Action func, RequestOptions options = null); /// 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); + Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null); /// Removes the permission overwrite for the given user, if one exists. - Task RemovePermissionOverwriteAsync(IUser user); + Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null); /// Adds or updates the permission overwrite for the given role. - Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions); + 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); + 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); + 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); + new Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); } } \ No newline at end of file diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 2fa09cd08..77ed811eb 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -8,26 +8,29 @@ namespace Discord public interface IMessageChannel : IChannel { /// Sends a message to this message channel. - Task SendMessageAsync(string text, bool isTTS = false); + Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. - Task SendFileAsync(string filePath, string text = null, bool isTTS = false); + Task SendFileAsync(string filePath, string text = null, bool isTTS = false, 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); + Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, 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); + 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, CacheMode mode = CacheMode.AllowDownload); + 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, CacheMode mode = CacheMode.AllowDownload); + 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, CacheMode mode = CacheMode.AllowDownload); + 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(); + Task> GetPinnedMessagesAsync(RequestOptions options = null); /// Bulk deletes multiple messages. - Task DeleteMessagesAsync(IEnumerable messages); + Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. - IDisposable EnterTypingState(); + IDisposable EnterTypingState(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index 3b4248b6e..7ecaf6d7b 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -10,6 +10,6 @@ namespace Discord string Topic { get; } /// Modifies this text channel. - Task ModifyAsync(Action func); + Task ModifyAsync(Action func, RequestOptions options = null); } } \ No newline at end of file diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs index 75f6a0190..d1be73072 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -13,7 +13,7 @@ namespace Discord int UserLimit { get; } /// Modifies this voice channel. - Task ModifyAsync(Action func); + Task ModifyAsync(Action func, RequestOptions options = null); /// Connects to this voice channel. Task ConnectAsync(); } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index f1b1da6a1..2ff7afa3f 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -53,56 +53,56 @@ namespace Discord IReadOnlyCollection Roles { get; } /// Modifies this guild. - Task ModifyAsync(Action func); + Task ModifyAsync(Action func, RequestOptions options = null); /// Modifies this guild's embed. - Task ModifyEmbedAsync(Action func); + Task ModifyEmbedAsync(Action func, RequestOptions options = null); /// Bulk modifies the channels of this guild. - Task ModifyChannelsAsync(IEnumerable args); + Task ModifyChannelsAsync(IEnumerable args, RequestOptions options = null); /// Bulk modifies the roles of this guild. - Task ModifyRolesAsync(IEnumerable args); + Task ModifyRolesAsync(IEnumerable args, RequestOptions options = null); /// Leaves this guild. If you are the owner, use Delete instead. - Task LeaveAsync(); + Task LeaveAsync(RequestOptions options = null); /// Gets a collection of all users banned on this guild. - Task> GetBansAsync(); + Task> GetBansAsync(RequestOptions options = null); /// Bans the provided user from this guild and optionally prunes their recent messages. - Task AddBanAsync(IUser user, int pruneDays = 0); + Task AddBanAsync(IUser user, int pruneDays = 0, RequestOptions options = null); /// Bans the provided user id from this guild and optionally prunes their recent messages. - Task AddBanAsync(ulong userId, int pruneDays = 0); + Task AddBanAsync(ulong userId, int pruneDays = 0, RequestOptions options = null); /// Unbans the provided user if it is currently banned. - Task RemoveBanAsync(IUser user); + Task RemoveBanAsync(IUser user, RequestOptions options = null); /// Unbans the provided user id if it is currently banned. - Task RemoveBanAsync(ulong userId); + Task RemoveBanAsync(ulong userId, RequestOptions options = null); /// Gets a collection of all channels in this guild. - Task> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload); + 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); + Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Creates a new text channel. - Task CreateTextChannelAsync(string name); + Task CreateTextChannelAsync(string name, RequestOptions options = null); /// Creates a new voice channel. - Task CreateVoiceChannelAsync(string name); + Task CreateVoiceChannelAsync(string name, RequestOptions options = null); - Task> GetIntegrationsAsync(); - Task CreateIntegrationAsync(ulong id, string type); + Task> GetIntegrationsAsync(RequestOptions options = null); + Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null); /// Gets a collection of all invites to this guild. - Task> GetInvitesAsync(); + Task> GetInvitesAsync(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); + 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); //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); + Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Gets the current user for this guild. - Task GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload); + Task GetCurrentUserAsync(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. - Task PruneUsersAsync(int days = 30, bool simulate = false); + Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); } } \ No newline at end of file diff --git a/src/Discord.Net.Core/Entities/IDeletable.cs b/src/Discord.Net.Core/Entities/IDeletable.cs index f35f8ad88..ba22a537a 100644 --- a/src/Discord.Net.Core/Entities/IDeletable.cs +++ b/src/Discord.Net.Core/Entities/IDeletable.cs @@ -5,6 +5,6 @@ namespace Discord public interface IDeletable { /// Deletes this object and all its children. - Task DeleteAsync(); + Task DeleteAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/IUpdateable.cs b/src/Discord.Net.Core/Entities/IUpdateable.cs index 50b23bb95..b0f51aee7 100644 --- a/src/Discord.Net.Core/Entities/IUpdateable.cs +++ b/src/Discord.Net.Core/Entities/IUpdateable.cs @@ -5,6 +5,6 @@ namespace Discord public interface IUpdateable { /// Updates this object's properties with its current state. - Task UpdateAsync(); + Task UpdateAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Invites/IInvite.cs b/src/Discord.Net.Core/Entities/Invites/IInvite.cs index 5e5ca40ae..081b57d76 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInvite.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInvite.cs @@ -15,6 +15,6 @@ namespace Discord ulong GuildId { get; } /// Accepts this invite and joins the target guild. This will fail on bot accounts. - Task AcceptAsync(); + Task AcceptAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index fd170dacb..3faa31419 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -7,11 +7,11 @@ namespace Discord public interface IUserMessage : IMessage, IDeletable { /// Modifies this message. - Task ModifyAsync(Action func); + Task ModifyAsync(Action func, RequestOptions options = null); /// Adds this message to its channel's pinned messages. - Task PinAsync(); + Task PinAsync(RequestOptions options = null); /// Removes this message from its channel's pinned messages. - Task UnpinAsync(); + Task UnpinAsync(RequestOptions options = null); /// Transforms this message's text into a human readable form, resolving mentions to that object's name. string Resolve(int startIndex, int length, diff --git a/src/Discord.Net.Core/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs index bcea9b17e..d7de45b88 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -1,4 +1,8 @@ -namespace Discord +using Discord.API.Rest; +using System; +using System.Threading.Tasks; + +namespace Discord { public interface IRole : ISnowflakeEntity, IDeletable, IMentionable { @@ -19,6 +23,6 @@ int Position { get; } ///// Modifies this role. - //Task ModifyAsync(Action func); + Task ModifyAsync(Action func, RequestOptions options = null); } } \ No newline at end of file diff --git a/src/Discord.Net.Core/Entities/Users/IGroupUser.cs b/src/Discord.Net.Core/Entities/Users/IGroupUser.cs index 3d18b7cd2..dd046a5a8 100644 --- a/src/Discord.Net.Core/Entities/Users/IGroupUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGroupUser.cs @@ -3,6 +3,6 @@ public interface IGroupUser : IUser, IVoiceState { ///// Kicks this user from this group. - //Task KickAsync(); + //Task KickAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 72cbabb30..7763a14ae 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -23,8 +23,8 @@ namespace Discord ChannelPermissions GetPermissions(IGuildChannel channel); /// Kicks this user from this guild. - Task KickAsync(); + Task KickAsync(RequestOptions options = null); /// Modifies this user's properties in this guild. - Task ModifyAsync(Action func); + Task ModifyAsync(Action func, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs index dea95b566..a6e8f80e8 100644 --- a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs +++ b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs @@ -13,7 +13,7 @@ namespace Discord /// Returns true if this user has enabled MFA on their account. bool IsMfaEnabled { get; } - Task ModifyAsync(Action func); - Task ModifyStatusAsync(Action func); + Task ModifyAsync(Action func, RequestOptions options = null); + Task ModifyStatusAsync(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 43f441e7e..c02f8aeca 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -18,8 +18,8 @@ namespace Discord string Username { get; } /// Returns a private message channel to this user, creating one if it does not already exist. - Task GetDMChannelAsync(CacheMode mode = CacheMode.AllowDownload); + Task GetDMChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Returns a private message channel to this user, creating one if it does not already exist. - Task CreateDMChannelAsync(); + Task CreateDMChannelAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 37c7ed6dd..319520386 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -12,67 +12,64 @@ namespace Discord.Rest internal static class ChannelHelper { //General - public static async Task GetAsync(IGuildChannel channel, BaseDiscordClient client) - { - return await client.ApiClient.GetChannelAsync(channel.GuildId, channel.Id).ConfigureAwait(false); - } - public static async Task GetAsync(IPrivateChannel channel, BaseDiscordClient client) - { - return await client.ApiClient.GetChannelAsync(channel.Id).ConfigureAwait(false); - } - public static async Task DeleteAsync(IChannel channel, BaseDiscordClient client) + public static async Task DeleteAsync(IChannel channel, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.DeleteChannelAsync(channel.Id).ConfigureAwait(false); + await client.ApiClient.DeleteChannelAsync(channel.Id, options).ConfigureAwait(false); } public static async Task ModifyAsync(IGuildChannel channel, BaseDiscordClient client, - Action func) + Action func, + RequestOptions options) { var args = new ModifyGuildChannelParams(); func(args); - await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); + await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); } public static async Task ModifyAsync(ITextChannel channel, BaseDiscordClient client, - Action func) + Action func, + RequestOptions options) { var args = new ModifyTextChannelParams(); func(args); - await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); + await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); } public static async Task ModifyAsync(IVoiceChannel channel, BaseDiscordClient client, - Action func) + Action func, + RequestOptions options) { var args = new ModifyVoiceChannelParams(); func(args); - await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args); + await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); } //Invites - public static async Task> GetInvitesAsync(IChannel channel, BaseDiscordClient client) + public static async Task> GetInvitesAsync(IChannel channel, BaseDiscordClient client, + RequestOptions options) { - var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id); + var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); } public static async Task CreateInviteAsync(IChannel channel, BaseDiscordClient client, - int? maxAge, int? maxUses, bool isTemporary) + int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) { var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; if (maxAge.HasValue) args.MaxAge = maxAge.Value; if (maxUses.HasValue) args.MaxUses = maxUses.Value; - var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args); + var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options); return RestInviteMetadata.Create(client, model); } //Messages public static async Task GetMessageAsync(IChannel channel, BaseDiscordClient client, - ulong id) + ulong id, RequestOptions options) { - var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id).ConfigureAwait(false); + var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); return RestMessage.Create(client, model); } public static IAsyncEnumerable> GetMessagesAsync(IChannel channel, BaseDiscordClient client, - ulong? fromMessageId = null, Direction dir = Direction.Before, int limit = DiscordConfig.MaxMessagesPerBatch) + ulong? fromMessageId, Direction dir, int limit, RequestOptions options) { //TODO: Test this with Around direction return new PagedAsyncEnumerable( @@ -86,7 +83,7 @@ namespace Discord.Rest }; if (info.Position != null) args.RelativeMessageId = info.Position.Value; - var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args); + var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options); return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); ; }, nextPage: (info, lastPage) => @@ -102,71 +99,72 @@ namespace Discord.Rest count: limit ); } - public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client) + public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client, + RequestOptions options) { - var models = await client.ApiClient.GetPinsAsync(channel.Id).ConfigureAwait(false); + var models = await client.ApiClient.GetPinsAsync(channel.Id, options).ConfigureAwait(false); return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); } public static async Task SendMessageAsync(IChannel channel, BaseDiscordClient client, - string text, bool isTTS) + string text, bool isTTS, RequestOptions options) { var args = new CreateMessageParams(text) { IsTTS = isTTS }; - var model = await client.ApiClient.CreateMessageAsync(channel.Id, args).ConfigureAwait(false); + var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, model); } public static Task SendFileAsync(IChannel channel, BaseDiscordClient client, - string filePath, string text, bool isTTS) + string filePath, string text, bool isTTS, RequestOptions options) { string filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) - return SendFileAsync(channel, client, file, filename, text, isTTS); + return SendFileAsync(channel, client, file, filename, text, isTTS, options); } public static async Task SendFileAsync(IChannel channel, BaseDiscordClient client, - Stream stream, string filename, string text, bool isTTS) + Stream stream, string filename, string text, bool isTTS, RequestOptions options) { var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await client.ApiClient.UploadFileAsync(channel.Id, args).ConfigureAwait(false); + var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, model); } public static async Task DeleteMessagesAsync(IChannel channel, BaseDiscordClient client, - IEnumerable messages) + IEnumerable messages, RequestOptions options) { var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); - await client.ApiClient.DeleteMessagesAsync(channel.Id, args).ConfigureAwait(false); + await client.ApiClient.DeleteMessagesAsync(channel.Id, args, options).ConfigureAwait(false); } //Permission Overwrites public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, - IUser user, OverwritePermissions perms) + IUser user, OverwritePermissions perms, RequestOptions options) { var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); - await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args).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) + IRole role, OverwritePermissions perms, RequestOptions options) { var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); - await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args).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) + IUser user, RequestOptions options) { - await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id).ConfigureAwait(false); + await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id, options).ConfigureAwait(false); } public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, - IRole role) + IRole role, RequestOptions options) { - await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id).ConfigureAwait(false); + await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id, options).ConfigureAwait(false); } //Users public static async Task GetUserAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, - ulong id) + ulong id, RequestOptions options) { - var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id); + var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id, options); if (model == null) return null; var user = RestGuildUser.Create(client, guild, model); @@ -176,7 +174,7 @@ namespace Discord.Rest return user; } public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, - ulong? froUserId = null, int? limit = DiscordConfig.MaxUsersPerBatch) + ulong? fromUserId, int? limit, RequestOptions options) { return new PagedAsyncEnumerable( DiscordConfig.MaxUsersPerBatch, @@ -188,7 +186,7 @@ namespace Discord.Rest }; if (info.Position != null) args.AfterUserId = info.Position.Value; - var models = await guild.Discord.ApiClient.GetGuildMembersAsync(guild.Id, args); + var models = await guild.Discord.ApiClient.GetGuildMembersAsync(guild.Id, args, options); return models .Select(x => RestGuildUser.Create(client, guild, x)) .Where(x => x.GetPermissions(channel).ReadMessages) @@ -200,13 +198,14 @@ namespace Discord.Rest if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) info.Remaining = 0; }, - start: froUserId, + start: fromUserId, count: limit ); } //Typing - public static IDisposable EnterTypingState(IChannel channel, BaseDiscordClient client) - => new TypingNotifier(client, channel); + public static IDisposable EnterTypingState(IChannel channel, BaseDiscordClient client, + RequestOptions options) + => new TypingNotifier(client, channel, options); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index 3d1233cef..6d2b729dd 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -7,21 +7,21 @@ namespace Discord.Rest public interface IRestMessageChannel : IMessageChannel { /// Sends a message to this message channel. - new Task SendMessageAsync(string text, bool isTTS = false); + new Task SendMessageAsync(string text, bool isTTS = false, 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); + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, 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); + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, RequestOptions options = null); /// Gets a message from this message channel with the given id, or null if not found. - Task GetMessageAsync(ulong id); + Task GetMessageAsync(ulong id, RequestOptions options = null); /// Gets the last N messages from this message channel. - IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch); + 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); + 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); + 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(); + new Task> GetPinnedMessagesAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 952a9e0fe..04305c6b8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -41,12 +41,12 @@ namespace Discord.Rest } internal abstract void Update(Model model); - public abstract Task UpdateAsync(); + public abstract Task UpdateAsync(RequestOptions options = null); //IChannel - Task IChannel.GetUserAsync(ulong id, CacheMode mode) + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overriden - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overriden } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 43f318abe..3adbd3fa4 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -34,10 +34,13 @@ namespace Discord.Rest Recipient.Update(model.Recipients.Value[0]); } - public override async Task UpdateAsync() - => Update(await ChannelHelper.GetAsync(this, Discord)); - public Task CloseAsync() - => ChannelHelper.DeleteAsync(this, Discord); + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetChannelAsync(Id, options); + Update(model); + } + public Task CloseAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); public RestUser GetUser(ulong id) { @@ -49,29 +52,29 @@ namespace Discord.Rest return null; } - public Task GetMessageAsync(ulong id) - => ChannelHelper.GetMessageAsync(this, Discord, id); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); - public Task> GetPinnedMessagesAsync() - => ChannelHelper.GetPinnedMessagesAsync(this, Discord); - - public Task SendMessageAsync(string text, bool isTTS) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); - public Task SendFileAsync(string filePath, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); - - public Task DeleteMessagesAsync(IEnumerable messages) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); - - public IDisposable EnterTypingState() - => ChannelHelper.EnterTypingState(this, Discord); + 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, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); public override string ToString() => $"@{Recipient}"; private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; @@ -86,49 +89,50 @@ namespace Discord.Rest IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); //IMessageChannel - async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id); + return await GetMessageAsync(id, options); else return null; } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit); + return GetMessagesAsync(limit, options); else return AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessageId, dir, limit); + return GetMessagesAsync(fromMessageId, dir, limit, options); else return AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessage, dir, limit); + return GetMessagesAsync(fromMessage, dir, limit, options); else return AsyncEnumerable.Empty>(); } - async Task> IMessageChannel.GetPinnedMessagesAsync() - => await GetPinnedMessagesAsync().ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) - => await SendFileAsync(filePath, text, isTTS); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => await SendFileAsync(stream, filename, text, isTTS); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) - => await SendMessageAsync(text, isTTS); - IDisposable IMessageChannel.EnterTypingState() - => EnterTypingState(); + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); //IChannel - Task IChannel.GetUserAsync(ulong id, CacheMode mode) + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index d06077a4a..4a1ea7b87 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -49,12 +49,15 @@ namespace Discord.Rest _users = users.ToImmutable(); } - public override async Task UpdateAsync() - => Update(await ChannelHelper.GetAsync(this, Discord)); - public Task LeaveAsync() - => ChannelHelper.DeleteAsync(this, Discord); + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetChannelAsync(Id, options); + Update(model); + } + public Task LeaveAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); - public IUser GetUser(ulong id) + public RestUser GetUser(ulong id) { RestGroupUser user; if (_users.TryGetValue(id, out user)) @@ -62,29 +65,29 @@ namespace Discord.Rest return null; } - public Task GetMessageAsync(ulong id) - => ChannelHelper.GetMessageAsync(this, Discord, id); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); - public Task> GetPinnedMessagesAsync() - => ChannelHelper.GetPinnedMessagesAsync(this, Discord); - - public Task SendMessageAsync(string text, bool isTTS) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); - public Task SendFileAsync(string filePath, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); - - public Task DeleteMessagesAsync(IEnumerable messages) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); - - public IDisposable EnterTypingState() - => ChannelHelper.EnterTypingState(this, Discord); + 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, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id}, Group)"; @@ -96,50 +99,50 @@ namespace Discord.Rest IReadOnlyCollection IPrivateChannel.Recipients => Recipients; //IMessageChannel - async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id); + return await GetMessageAsync(id, options); else return null; } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit); + return GetMessagesAsync(limit, options); else return AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessageId, dir, limit); + return GetMessagesAsync(fromMessageId, dir, limit, options); else return AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessage, dir, limit); + return GetMessagesAsync(fromMessage, dir, limit, options); else return AsyncEnumerable.Empty>(); } - async Task> IMessageChannel.GetPinnedMessagesAsync() - => await GetPinnedMessagesAsync(); - - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) - => await SendFileAsync(filePath, text, isTTS); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => await SendFileAsync(stream, filename, text, isTTS); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) - => await SendMessageAsync(text, isTTS); - IDisposable IMessageChannel.EnterTypingState() - => EnterTypingState(); + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); //IChannel - Task IChannel.GetUserAsync(ulong id, CacheMode mode) - => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + 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.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 72dd4a48b..08ef88d46 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -49,12 +49,15 @@ namespace Discord.Rest _overwrites = newOverwrites.ToImmutable(); } - public override async Task UpdateAsync() - => Update(await ChannelHelper.GetAsync(this, Discord)); - public Task ModifyAsync(Action func) - => ChannelHelper.ModifyAsync(this, Discord, func); - public Task DeleteAsync() - => ChannelHelper.DeleteAsync(this, Discord); + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetChannelAsync(GuildId, Id, options); + Update(model); + } + 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) { @@ -74,19 +77,19 @@ namespace Discord.Rest } return null; } - public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms) + public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, RequestOptions options = null) { - await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms).ConfigureAwait(false); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms, options).ConfigureAwait(false); _overwrites = _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User })); } - public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms) + public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, RequestOptions options = null) { - await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms).ConfigureAwait(false); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms, options).ConfigureAwait(false); _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role })); } - public async Task RemovePermissionOverwriteAsync(IUser user) + public async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) { - await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user).ConfigureAwait(false); + await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false); for (int i = 0; i < _overwrites.Length; i++) { @@ -97,9 +100,9 @@ namespace Discord.Rest } } } - public async Task RemovePermissionOverwriteAsync(IRole role) + public async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) { - await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role).ConfigureAwait(false); + await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false); for (int i = 0; i < _overwrites.Length; i++) { @@ -111,41 +114,41 @@ namespace Discord.Rest } } - public async Task> GetInvitesAsync() - => await ChannelHelper.GetInvitesAsync(this, Discord); - public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) - => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); + public async Task> GetInvitesAsync(RequestOptions options = null) + => await ChannelHelper.GetInvitesAsync(this, Discord, options); + public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true, RequestOptions options = null) + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options); public override string ToString() => Name; //IGuildChannel - async Task> IGuildChannel.GetInvitesAsync() - => await GetInvitesAsync(); - async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) - => await CreateInviteAsync(maxAge, maxUses, isTemporary); + async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options); + async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) + => await CreateInviteAsync(maxAge, maxUses, isTemporary, options); OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) => GetPermissionOverwrite(role); OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) => GetPermissionOverwrite(user); - async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions) - => await AddPermissionOverwriteAsync(role, permissions); - async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions) - => await AddPermissionOverwriteAsync(user, permissions); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role) - => await RemovePermissionOverwriteAsync(role); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) - => await RemovePermissionOverwriteAsync(user); + async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) + => await AddPermissionOverwriteAsync(role, permissions, options); + async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) + => await AddPermissionOverwriteAsync(user, permissions, options); + async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) + => await RemovePermissionOverwriteAsync(role, options); + async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) + => await RemovePermissionOverwriteAsync(user, options); - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overriden //Overriden in Text/Voice //TODO: Does this actually override? - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? //IChannel - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overriden in Text/Voice //TODO: Does this actually override? - Task IChannel.GetUserAsync(ulong id, CacheMode mode) + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 77677a5bf..b90fff58f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -34,95 +34,95 @@ namespace Discord.Rest } - public Task ModifyAsync(Action func) - => ChannelHelper.ModifyAsync(this, Discord, func); + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); - public Task GetUserAsync(ulong id) - => ChannelHelper.GetUserAsync(this, Guild, Discord, id); - public IAsyncEnumerable> GetUsersAsync() - => ChannelHelper.GetUsersAsync(this, Guild, Discord); + 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 Task GetMessageAsync(ulong id) - => ChannelHelper.GetMessageAsync(this, Discord, id); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, limit: limit); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit); - public Task> GetPinnedMessagesAsync() - => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + 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, bool isTTS) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); - public Task SendFileAsync(string filePath, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); - public IDisposable EnterTypingState() - => ChannelHelper.EnterTypingState(this, Discord); + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); private string DebuggerDisplay => $"{Name} ({Id}, Text)"; //IGuildChannel - async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetUserAsync(id); + return await GetUserAsync(id, options); else return null; } - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetUsersAsync(); + return GetUsersAsync(options); else return AsyncEnumerable.Empty>(); //Overriden } //IMessageChannel - async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id); + return await GetMessageAsync(id, options); else return null; } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit); + return GetMessagesAsync(limit, options); else return AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessageId, dir, limit); + return GetMessagesAsync(fromMessageId, dir, limit, options); else return AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessage, dir, limit); + return GetMessagesAsync(fromMessage, dir, limit, options); else return AsyncEnumerable.Empty>(); } - async Task> IMessageChannel.GetPinnedMessagesAsync() - => await GetPinnedMessagesAsync(); + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) - => await SendFileAsync(filePath, text, isTTS); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => await SendFileAsync(stream, filename, text, isTTS); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) - => await SendMessageAsync(text, isTTS); - IDisposable IMessageChannel.EnterTypingState() - => EnterTypingState(); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 02eab4776..8abd3c911 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -33,8 +33,8 @@ namespace Discord.Rest UserLimit = model.UserLimit.Value; } - public Task ModifyAsync(Action func) - => ChannelHelper.ModifyAsync(this, Discord, func); + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; @@ -42,9 +42,9 @@ namespace Discord.Rest Task IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } //IGuildChannel - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index e6aec2f70..966914960 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -14,7 +14,7 @@ namespace Discord.Rest { //General public static async Task ModifyAsync(IGuild guild, BaseDiscordClient client, - Action func) + Action func, RequestOptions options) { if (func == null) throw new NullReferenceException(nameof(func)); @@ -26,116 +26,122 @@ namespace Discord.Rest if (args.Icon.IsSpecified && guild.IconId != null) args.Icon = new API.Image(guild.IconId); - return await client.ApiClient.ModifyGuildAsync(guild.Id, args).ConfigureAwait(false); + return await client.ApiClient.ModifyGuildAsync(guild.Id, args, options).ConfigureAwait(false); } public static async Task ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, - Action func) + Action func, RequestOptions options) { if (func == null) throw new NullReferenceException(nameof(func)); var args = new ModifyGuildEmbedParams(); func(args); - return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args).ConfigureAwait(false); + return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, args, options).ConfigureAwait(false); } public static async Task ModifyChannelsAsync(IGuild guild, BaseDiscordClient client, - IEnumerable args) + IEnumerable args, RequestOptions options) { - await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args).ConfigureAwait(false); + await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, args, options).ConfigureAwait(false); } public static async Task> ModifyRolesAsync(IGuild guild, BaseDiscordClient client, - IEnumerable args) + IEnumerable args, RequestOptions options) { - return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args).ConfigureAwait(false); + return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, args, options).ConfigureAwait(false); } - public static async Task LeaveAsync(IGuild guild, BaseDiscordClient client) + public static async Task LeaveAsync(IGuild guild, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.LeaveGuildAsync(guild.Id).ConfigureAwait(false); + await client.ApiClient.LeaveGuildAsync(guild.Id, options).ConfigureAwait(false); } - public static async Task DeleteAsync(IGuild guild, BaseDiscordClient client) + public static async Task DeleteAsync(IGuild guild, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.DeleteGuildAsync(guild.Id).ConfigureAwait(false); + await client.ApiClient.DeleteGuildAsync(guild.Id, options).ConfigureAwait(false); } //Bans - public static async Task> GetBansAsync(IGuild guild, BaseDiscordClient client) + public static async Task> GetBansAsync(IGuild guild, BaseDiscordClient client, + RequestOptions options) { - var models = await client.ApiClient.GetGuildBansAsync(guild.Id); + var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); } public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client, - ulong userId, int pruneDays) + ulong userId, int pruneDays, RequestOptions options) { var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; - await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args); + await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args, options); } public static async Task RemoveBanAsync(IGuild guild, BaseDiscordClient client, - ulong userId) + ulong userId, RequestOptions options) { - await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId); + await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId, options); } //Channels public static async Task GetChannelAsync(IGuild guild, BaseDiscordClient client, - ulong id) + ulong id, RequestOptions options) { - var model = await client.ApiClient.GetChannelAsync(guild.Id, id).ConfigureAwait(false); + var model = await client.ApiClient.GetChannelAsync(guild.Id, id, options).ConfigureAwait(false); if (model != null) return RestGuildChannel.Create(client, guild, model); return 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).ConfigureAwait(false); + 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) + string name, RequestOptions options) { if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams(name, ChannelType.Text); - var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); + 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) + string name, RequestOptions options) { if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams(name, ChannelType.Voice); - var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args).ConfigureAwait(false); + var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestVoiceChannel.Create(client, guild, model); } //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).ConfigureAwait(false); + var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestGuildIntegration.Create(client, x)).ToImmutableArray(); } public static async Task CreateIntegrationAsync(IGuild guild, BaseDiscordClient client, - ulong id, string type) + ulong id, string type, RequestOptions options) { var args = new CreateGuildIntegrationParams(id, type); - var model = await client.ApiClient.CreateGuildIntegrationAsync(guild.Id, args).ConfigureAwait(false); + var model = await client.ApiClient.CreateGuildIntegrationAsync(guild.Id, args, options).ConfigureAwait(false); return RestGuildIntegration.Create(client, 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).ConfigureAwait(false); + var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); } //Roles public static async Task CreateRoleAsync(IGuild guild, BaseDiscordClient client, - string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) + string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) { if (name == null) throw new ArgumentNullException(nameof(name)); - var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id).ConfigureAwait(false); + var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false); var role = RestRole.Create(client, model); await role.ModifyAsync(x => @@ -144,26 +150,27 @@ namespace Discord.Rest x.Permissions = (permissions ?? role.Permissions).RawValue; x.Color = (color ?? Color.Default).RawValue; x.Hoist = isHoisted; - }).ConfigureAwait(false); + }, options).ConfigureAwait(false); return role; } //Users public static async Task GetUserAsync(IGuild guild, BaseDiscordClient client, - ulong id) + ulong id, RequestOptions options) { - var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id).ConfigureAwait(false); + var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id, options).ConfigureAwait(false); if (model != null) return RestGuildUser.Create(client, guild, model); return null; } - public static async Task GetCurrentUserAsync(IGuild guild, BaseDiscordClient client) + public static async Task GetCurrentUserAsync(IGuild guild, BaseDiscordClient client, + RequestOptions options) { - return await GetUserAsync(guild, client, client.CurrentUser.Id).ConfigureAwait(false); + return await GetUserAsync(guild, client, client.CurrentUser.Id, options).ConfigureAwait(false); } public static IAsyncEnumerable> GetUsersAsync(IGuild guild, BaseDiscordClient client, - ulong? fromUserId = null, int limit = DiscordConfig.MaxMessagesPerBatch) + ulong? fromUserId, int? limit, RequestOptions options) { return new PagedAsyncEnumerable( DiscordConfig.MaxMessagesPerBatch, @@ -175,7 +182,7 @@ namespace Discord.Rest }; if (info.Position != null) args.AfterUserId = info.Position.Value; - var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args); + var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args, options); return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray(); }, nextPage: (info, lastPage) => @@ -189,14 +196,14 @@ namespace Discord.Rest ); } public static async Task PruneUsersAsync(IGuild guild, BaseDiscordClient client, - int days = 30, bool simulate = false) + int days, bool simulate, RequestOptions options) { var args = new GuildPruneParams(days); GetGuildPruneCountResponse model; if (simulate) - model = await client.ApiClient.GetGuildPruneCountAsync(guild.Id, args).ConfigureAwait(false); + model = await client.ApiClient.GetGuildPruneCountAsync(guild.Id, args, options).ConfigureAwait(false); else - model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args).ConfigureAwait(false); + model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args, options).ConfigureAwait(false); return model.Pruned; } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 8f5d269e5..a51f60d5a 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; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Guild; @@ -93,56 +92,56 @@ namespace Discord.Rest } //General - public async Task UpdateAsync() - => Update(await Discord.ApiClient.GetGuildAsync(Id)); - public Task DeleteAsync() - => GuildHelper.DeleteAsync(this, Discord); - - public Task ModifyAsync(Action func) - => GuildHelper.ModifyAsync(this, Discord, func); - public Task ModifyEmbedAsync(Action func) - => GuildHelper.ModifyEmbedAsync(this, Discord, func); - public Task ModifyChannelsAsync(IEnumerable args) - => GuildHelper.ModifyChannelsAsync(this, Discord, args); - public Task ModifyRolesAsync(IEnumerable args) - => GuildHelper.ModifyRolesAsync(this, Discord, args); - - public Task LeaveAsync() - => GuildHelper.LeaveAsync(this, Discord); + public async Task UpdateAsync(RequestOptions options = null) + => Update(await Discord.ApiClient.GetGuildAsync(Id, options)); + 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 ModifyChannelsAsync(IEnumerable args, RequestOptions options = null) + => GuildHelper.ModifyChannelsAsync(this, Discord, args, options); + public Task ModifyRolesAsync(IEnumerable args, RequestOptions options = null) + => GuildHelper.ModifyRolesAsync(this, Discord, args, options); + + public Task LeaveAsync(RequestOptions options = null) + => GuildHelper.LeaveAsync(this, Discord, options); //Bans - public Task> GetBansAsync() - => GuildHelper.GetBansAsync(this, Discord); + public Task> GetBansAsync(RequestOptions options = null) + => GuildHelper.GetBansAsync(this, Discord, options); - public Task AddBanAsync(IUser user, int pruneDays = 0) - => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays); - public Task AddBanAsync(ulong userId, int pruneDays = 0) - => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays); + public Task AddBanAsync(IUser user, int pruneDays = 0, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, options); + public Task AddBanAsync(ulong userId, int pruneDays = 0, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, options); - public Task RemoveBanAsync(IUser user) - => GuildHelper.RemoveBanAsync(this, Discord, user.Id); - public Task RemoveBanAsync(ulong userId) - => GuildHelper.RemoveBanAsync(this, Discord, userId); + 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() - => GuildHelper.GetChannelsAsync(this, Discord); - public Task GetChannelAsync(ulong id) - => GuildHelper.GetChannelAsync(this, Discord, id); - public Task CreateTextChannelAsync(string name) - => GuildHelper.CreateTextChannelAsync(this, Discord, name); - public Task CreateVoiceChannelAsync(string name) - => GuildHelper.CreateVoiceChannelAsync(this, Discord, name); + 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 Task CreateTextChannelAsync(string name, RequestOptions options = null) + => GuildHelper.CreateTextChannelAsync(this, Discord, name, options); + public Task CreateVoiceChannelAsync(string name, RequestOptions options = null) + => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); //Integrations - public Task> GetIntegrationsAsync() - => GuildHelper.GetIntegrationsAsync(this, Discord); - public Task CreateIntegrationAsync(ulong id, string type) - => GuildHelper.CreateIntegrationAsync(this, Discord, id, type); + 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() - => GuildHelper.GetInvitesAsync(this, Discord); + public Task> GetInvitesAsync(RequestOptions options = null) + => GuildHelper.GetInvitesAsync(this, Discord, options); //Roles public RestRole GetRole(ulong id) @@ -153,23 +152,24 @@ namespace Discord.Rest return null; } - public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) + 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); + var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options); _roles = _roles.Add(role.Id, role); return role; } //Users - public IAsyncEnumerable> GetUsersAsync() - => GuildHelper.GetUsersAsync(this, Discord); - public Task GetUserAsync(ulong id) - => GuildHelper.GetUserAsync(this, Discord, id); - public Task GetCurrentUserAsync() - => GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id); + 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 PruneUsersAsync(int days = 30, bool simulate = false) - => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); + public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) + => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; @@ -180,59 +180,59 @@ namespace Discord.Rest IRole IGuild.EveryoneRole => EveryoneRole; IReadOnlyCollection IGuild.Roles => Roles; - async Task> IGuild.GetBansAsync() - => await GetBansAsync(); + async Task> IGuild.GetBansAsync(RequestOptions options) + => await GetBansAsync(options); - async Task> IGuild.GetChannelsAsync(CacheMode mode) + async Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetChannelsAsync(); + return await GetChannelsAsync(options); else return ImmutableArray.Create(); } - async Task IGuild.GetChannelAsync(ulong id, CacheMode mode) + async Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetChannelAsync(id); + return await GetChannelAsync(id, options); else return null; } - async Task IGuild.CreateTextChannelAsync(string name) - => await CreateTextChannelAsync(name); - async Task IGuild.CreateVoiceChannelAsync(string name) - => await CreateVoiceChannelAsync(name); + async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options) + => await CreateTextChannelAsync(name, options); + async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) + => await CreateVoiceChannelAsync(name, options); - async Task> IGuild.GetIntegrationsAsync() - => await GetIntegrationsAsync(); - async Task IGuild.CreateIntegrationAsync(ulong id, string type) - => await CreateIntegrationAsync(id, type); + async Task> IGuild.GetIntegrationsAsync(RequestOptions options) + => await GetIntegrationsAsync(options); + async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) + => await CreateIntegrationAsync(id, type, options); - async Task> IGuild.GetInvitesAsync() - => await GetInvitesAsync(); + async Task> IGuild.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options); IRole IGuild.GetRole(ulong id) => GetRole(id); - async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted) - => await CreateRoleAsync(name, permissions, color, isHoisted); + async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) + => await CreateRoleAsync(name, permissions, color, isHoisted, options); - async Task IGuild.GetUserAsync(ulong id, CacheMode mode) + async Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetUserAsync(id); + return await GetUserAsync(id, options); else return null; } - async Task IGuild.GetCurrentUserAsync(CacheMode mode) + async Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetCurrentUserAsync(); + return await GetCurrentUserAsync(options); else return null; } - async Task> IGuild.GetUsersAsync(CacheMode mode) + async Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return (await GetUsersAsync().Flatten()).ToImmutableArray(); + return (await GetUsersAsync(options).Flatten()).ToImmutableArray(); else return ImmutableArray.Create(); } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index d222c2d1d..c6334bfb1 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -34,13 +34,13 @@ namespace Discord.Rest Permissions = new GuildPermissions(model.Permissions); } - public async Task LeaveAsync() + public async Task LeaveAsync(RequestOptions options = null) { - await Discord.ApiClient.LeaveGuildAsync(Id).ConfigureAwait(false); + await Discord.ApiClient.LeaveGuildAsync(Id, options).ConfigureAwait(false); } - public async Task DeleteAsync() + public async Task DeleteAsync(RequestOptions options = null) { - await Discord.ApiClient.DeleteGuildAsync(Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteGuildAsync(Id, options).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs index 23e0ddab7..8ec428178 100644 --- a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs +++ b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs @@ -5,17 +5,15 @@ namespace Discord.Rest { internal static class InviteHelper { - public static async Task GetAsync(IInvite invite, BaseDiscordClient client) + public static async Task AcceptAsync(IInvite invite, BaseDiscordClient client, + RequestOptions options) { - return await client.ApiClient.GetInviteAsync(invite.Code).ConfigureAwait(false); + await client.ApiClient.AcceptInviteAsync(invite.Code, options).ConfigureAwait(false); } - public static async Task AcceptAsync(IInvite invite, BaseDiscordClient client) + public static async Task DeleteAsync(IInvite invite, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.AcceptInviteAsync(invite.Code).ConfigureAwait(false); - } - public static async Task DeleteAsync(IInvite invite, BaseDiscordClient client) - { - await client.ApiClient.DeleteInviteAsync(invite.Code).ConfigureAwait(false); + 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 983b2986b..4c07ea9ac 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -32,14 +32,17 @@ namespace Discord.Rest GuildName = model.Guild.Name; ChannelName = model.Channel.Name; } + + public async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetInviteAsync(Code, options); + Update(model); + } + public Task DeleteAsync(RequestOptions options = null) + => InviteHelper.DeleteAsync(this, Discord, options); - public async Task UpdateAsync() - => Update(await InviteHelper.GetAsync(this, Discord).ConfigureAwait(false)); - public Task DeleteAsync() - => InviteHelper.DeleteAsync(this, Discord); - - public Task AcceptAsync() - => InviteHelper.AcceptAsync(this, Discord); + public Task AcceptAsync(RequestOptions options = null) + => InviteHelper.AcceptAsync(this, Discord, options); public override string ToString() => Url; private string DebuggerDisplay => $"{Url} ({GuildName} / {ChannelName})"; diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 28338635b..6918dfb27 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -6,28 +6,28 @@ namespace Discord.Rest { internal static class MessageHelper { - public static async Task GetAsync(IMessage msg, BaseDiscordClient client) - { - await client.ApiClient.GetChannelMessageAsync(msg.ChannelId, msg.Id); - } - public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func) + public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, + RequestOptions options) { var args = new ModifyMessageParams(); func(args); - await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args); + await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args, options); } - public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client) + public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id); + await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id, options); } - public static async Task PinAsync(IMessage msg, BaseDiscordClient client) + public static async Task PinAsync(IMessage msg, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id); + await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id, options); } - public static async Task UnpinAsync(IMessage msg, BaseDiscordClient client) + public static async Task UnpinAsync(IMessage msg, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id); + await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id, options); } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index bf1a1e2a6..7096d6198 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -48,9 +48,9 @@ namespace Discord.Rest Content = model.Content.Value; } - public async Task UpdateAsync() + public async Task UpdateAsync(RequestOptions options) { - var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id).ConfigureAwait(false); + var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id, options).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index e21295a92..b28b321bf 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -109,15 +109,15 @@ namespace Discord.Rest } } - public Task ModifyAsync(Action func) - => MessageHelper.ModifyAsync(this, Discord, func); - public Task DeleteAsync() - => MessageHelper.DeleteAsync(this, Discord); + public Task ModifyAsync(Action func, RequestOptions options) + => MessageHelper.ModifyAsync(this, Discord, func, options); + public Task DeleteAsync(RequestOptions options) + => MessageHelper.DeleteAsync(this, Discord, options); - public Task PinAsync() - => MessageHelper.PinAsync(this, Discord); - public Task UnpinAsync() - => MessageHelper.UnpinAsync(this, Discord); + public Task PinAsync(RequestOptions options) + => MessageHelper.PinAsync(this, Discord, options); + public Task UnpinAsync(RequestOptions options) + => MessageHelper.UnpinAsync(this, Discord, options); public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index d2c3e01bd..e8aaaa772 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -40,10 +40,10 @@ namespace Discord.Rest Permissions = new GuildPermissions(model.Permissions); } - public Task ModifyAsync(Action func) - => RoleHelper.ModifyAsync(this, Discord, func); - public Task DeleteAsync() - => RoleHelper.DeleteAsync(this, Discord); + public Task ModifyAsync(Action func, RequestOptions options) + => RoleHelper.ModifyAsync(this, Discord, func, options); + public Task DeleteAsync(RequestOptions options) + => RoleHelper.DeleteAsync(this, Discord, options); public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs index c9438f10d..8ecc63893 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -7,16 +7,17 @@ namespace Discord.Rest internal static class RoleHelper { //General - public static async Task DeleteAsync(IRole role, BaseDiscordClient client) + public static async Task DeleteAsync(IRole role, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id).ConfigureAwait(false); + await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id, options).ConfigureAwait(false); } public static async Task ModifyAsync(IRole role, BaseDiscordClient client, - Action func) + Action func, RequestOptions options) { var args = new ModifyGuildRoleParams(); func(args); - await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, args); + await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, args, options); } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index dd6056f41..1ea4e2117 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -57,13 +57,16 @@ namespace Discord.Rest roles.Add(roleIds[i]); _roleIds = roles.ToImmutable(); } - - public override async Task UpdateAsync() - => Update(await UserHelper.GetAsync(this, Discord)); - public Task ModifyAsync(Action func) - => UserHelper.ModifyAsync(this, Discord, func); - public Task KickAsync() - => UserHelper.KickAsync(this, Discord); + + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetGuildMemberAsync(GuildId, Id, options); + Update(model); + } + public Task ModifyAsync(Action func, RequestOptions options = null) + => UserHelper.ModifyAsync(this, Discord, func, options); + public Task KickAsync(RequestOptions options = null) + => UserHelper.KickAsync(this, Discord, options); public ChannelPermissions GetPermissions(IGuildChannel channel) { diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs index 17c24e7d2..4dabfaa1c 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -35,11 +35,21 @@ namespace Discord.Rest IsMfaEnabled = model.MfaEnabled.Value; } - public override async Task UpdateAsync() - => Update(await UserHelper.GetAsync(this, Discord)); - public Task ModifyAsync(Action func) - => UserHelper.ModifyAsync(this, Discord, func); + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetMyUserAsync(options); + if (model.Id != Id) + 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."); + await UserHelper.ModifyAsync(this, Discord, func, options); + } - Task ISelfUser.ModifyStatusAsync(Action func) { throw new NotSupportedException(); } + Task ISelfUser.ModifyStatusAsync(Action func, RequestOptions options) { throw new NotSupportedException(); } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 46cd246bf..5e085b821 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -39,20 +39,23 @@ namespace Discord.Rest if (model.Username.IsSpecified) Username = model.Username.Value; } - - public virtual async Task UpdateAsync() - => Update(await UserHelper.GetAsync(this, Discord)); - public Task CreateDMChannelAsync() - => UserHelper.CreateDMChannelAsync(this, Discord); + public virtual async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetUserAsync(Id, options); + Update(model); + } + + public Task CreateDMChannelAsync(RequestOptions options = null) + => UserHelper.CreateDMChannelAsync(this, Discord, options); public override string ToString() => $"{Username}#{Discriminator}"; internal string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; //IUser - Task IUser.GetDMChannelAsync(CacheMode mode) + Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(null); - async Task IUser.CreateDMChannelAsync() - => await CreateDMChannelAsync(); + async Task IUser.CreateDMChannelAsync(RequestOptions options) + => await CreateDMChannelAsync(options); } } diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index a7ee241f5..fee3fd876 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -1,53 +1,37 @@ using Discord.API.Rest; using System; using System.Threading.Tasks; -using MemberModel = Discord.API.GuildMember; -using Model = Discord.API.User; namespace Discord.Rest { internal static class UserHelper { - public static async Task GetAsync(IUser user, BaseDiscordClient client) + public static async Task ModifyAsync(ISelfUser user, BaseDiscordClient client, Action func, + RequestOptions options) { - return await client.ApiClient.GetUserAsync(user.Id); - } - public static async Task GetAsync(ISelfUser user, BaseDiscordClient client) - { - var model = await client.ApiClient.GetMyUserAsync(); - if (model.Id != user.Id) - throw new InvalidOperationException("Unable to update this object using a different token."); - return model; - } - public static async Task GetAsync(IGuildUser user, BaseDiscordClient client) - { - return await client.ApiClient.GetGuildMemberAsync(user.GuildId, user.Id); - } - public static async Task ModifyAsync(ISelfUser user, BaseDiscordClient client, Action func) - { - if (user.Id != client.CurrentUser.Id) - throw new InvalidOperationException("Unable to modify this object using a different token."); - var args = new ModifyCurrentUserParams(); func(args); - await client.ApiClient.ModifySelfAsync(args); + await client.ApiClient.ModifySelfAsync(args, options); } - 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 ModifyGuildMemberParams(); func(args); - await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args); + await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args, options); } - public static async Task KickAsync(IGuildUser user, BaseDiscordClient client) + public static async Task KickAsync(IGuildUser user, BaseDiscordClient client, + RequestOptions options) { - await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id); + await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, options); } - public static async Task CreateDMChannelAsync(IUser user, BaseDiscordClient client) + 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)); + return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args, options)); } } } diff --git a/src/Discord.Net.Rest/Utils/TypingNotifier.cs b/src/Discord.Net.Rest/Utils/TypingNotifier.cs index 28da4fb75..62d19b5a1 100644 --- a/src/Discord.Net.Rest/Utils/TypingNotifier.cs +++ b/src/Discord.Net.Rest/Utils/TypingNotifier.cs @@ -9,12 +9,14 @@ namespace Discord.Rest private readonly BaseDiscordClient _client; private readonly CancellationTokenSource _cancelToken; private readonly ulong _channelId; + private readonly RequestOptions _options; - public TypingNotifier(BaseDiscordClient discord, IChannel channel) + public TypingNotifier(BaseDiscordClient discord, IChannel channel, RequestOptions options) { _client = discord; _cancelToken = new CancellationTokenSource(); _channelId = channel.Id; + _options = options; var _ = Run(); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index 00833717a..7ba08544b 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -11,11 +11,11 @@ namespace Discord.WebSocket IReadOnlyCollection CachedMessages { get; } /// Sends a message to this message channel. - new Task SendMessageAsync(string text, bool isTTS = false); + new Task SendMessageAsync(string text, bool isTTS = false, 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); + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, 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); + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, RequestOptions options = null); SocketMessage GetCachedMessage(ulong id); /// Gets the last N messages from this message channel. @@ -25,6 +25,6 @@ namespace Discord.WebSocket /// Gets a collection of messages in this channel. IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of pinned messages in this channel. - new Task> GetPinnedMessagesAsync(); + new Task> GetPinnedMessagesAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 86a649367..998e576f2 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -39,9 +39,9 @@ namespace Discord.WebSocket internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; //IChannel - Task IChannel.GetUserAsync(ulong id, CacheMode mode) + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + 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 47c290452..887499c59 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -9,7 +9,7 @@ namespace Discord.WebSocket internal static class SocketChannelHelper { public static IAsyncEnumerable> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, - ulong? fromMessageId, Direction dir, int limit, CacheMode mode) + ulong? fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { IReadOnlyCollection cachedMessages; IAsyncEnumerable> result; @@ -28,7 +28,7 @@ namespace Discord.WebSocket fromMessageId = cachedMessages.Min(x => x.Id); else fromMessageId = cachedMessages.Max(x => x.Id); - var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit); + var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options); return result.Concat(downloadedMessages); } public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 872973627..375dce605 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -38,46 +38,46 @@ namespace Discord.WebSocket Recipient.Update(state, model.Recipients.Value[0]); } - public Task CloseAsync() - => ChannelHelper.DeleteAsync(this, Discord); + public Task CloseAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); //Messages public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); - public async Task GetMessageAsync(ulong 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); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); return msg; } - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + 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() - => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); - public Task SendFileAsync(string filePath, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); - public IDisposable EnterTypingState() - => ChannelHelper.EnterTypingState(this, Discord); + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) => _messages.Add(msg); @@ -111,36 +111,36 @@ namespace Discord.WebSocket //IPrivateChannel IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); - + //IMessageChannel - async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id); + return await GetMessageAsync(id, options); else return GetCachedMessage(id); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode); - async Task> IMessageChannel.GetPinnedMessagesAsync() - => await GetPinnedMessagesAsync().ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) - => await SendFileAsync(filePath, text, isTTS); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => await SendFileAsync(stream, filename, text, isTTS); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) - => await SendMessageAsync(text, isTTS); - IDisposable IMessageChannel.EnterTypingState() - => EnterTypingState(); + 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, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); //IChannel - Task IChannel.GetUserAsync(ulong id, CacheMode mode) + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + 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 a096e169e..732296ff0 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -61,46 +61,46 @@ namespace Discord.WebSocket _users = users; } - public Task LeaveAsync() - => ChannelHelper.DeleteAsync(this, Discord); + public Task LeaveAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); //Messages public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); - public async Task GetMessageAsync(ulong 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); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); return msg; } - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + 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() - => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); - public Task SendFileAsync(string filePath, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); - public IDisposable EnterTypingState() - => ChannelHelper.EnterTypingState(this, Discord); + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) => _messages.Add(msg); @@ -176,35 +176,34 @@ namespace Discord.WebSocket IReadOnlyCollection IPrivateChannel.Recipients => Recipients; //IMessageChannel - async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id); + return await GetMessageAsync(id, options); else return GetCachedMessage(id); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode); - async Task> IMessageChannel.GetPinnedMessagesAsync() - => await GetPinnedMessagesAsync(); - - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) - => await SendFileAsync(filePath, text, isTTS); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => await SendFileAsync(stream, filename, text, isTTS); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) - => await SendMessageAsync(text, isTTS); - IDisposable IMessageChannel.EnterTypingState() - => EnterTypingState(); + 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, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); //IChannel - Task IChannel.GetUserAsync(ulong id, CacheMode mode) + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + 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 c3a33c3ea..637f7c8ff 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -51,10 +51,10 @@ namespace Discord.WebSocket _overwrites = newOverwrites.ToImmutable(); } - public Task ModifyAsync(Action func) - => ChannelHelper.ModifyAsync(this, Discord, func); - public Task DeleteAsync() - => ChannelHelper.DeleteAsync(this, Discord); + 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) { @@ -74,19 +74,19 @@ namespace Discord.WebSocket } return null; } - public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms) + public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, RequestOptions options = null) { - await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms).ConfigureAwait(false); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms, options).ConfigureAwait(false); _overwrites = _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User })); } - public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms) + public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, RequestOptions options = null) { - await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms).ConfigureAwait(false); + await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms, options).ConfigureAwait(false); _overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role })); } - public async Task RemovePermissionOverwriteAsync(IUser user) + public async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) { - await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user).ConfigureAwait(false); + await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false); for (int i = 0; i < _overwrites.Length; i++) { @@ -97,9 +97,9 @@ namespace Discord.WebSocket } } } - public async Task RemovePermissionOverwriteAsync(IRole role) + public async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) { - await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role).ConfigureAwait(false); + await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false); for (int i = 0; i < _overwrites.Length; i++) { @@ -111,10 +111,10 @@ namespace Discord.WebSocket } } - public async Task> GetInvitesAsync() - => await ChannelHelper.GetInvitesAsync(this, Discord); - public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true) - => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary); + public async Task> GetInvitesAsync(RequestOptions options = null) + => await ChannelHelper.GetInvitesAsync(this, Discord, options); + public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true, RequestOptions options = null) + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options); public new abstract SocketGuildUser GetUser(ulong id); @@ -128,33 +128,33 @@ namespace Discord.WebSocket //IGuildChannel ulong IGuildChannel.GuildId => Guild.Id; - async Task> IGuildChannel.GetInvitesAsync() - => await GetInvitesAsync(); - async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary) - => await CreateInviteAsync(maxAge, maxUses, isTemporary); + async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options); + async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) + => await CreateInviteAsync(maxAge, maxUses, isTemporary, options); OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) => GetPermissionOverwrite(role); OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) => GetPermissionOverwrite(user); - async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions) - => await AddPermissionOverwriteAsync(role, permissions); - async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions) - => await AddPermissionOverwriteAsync(user, permissions); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role) - => await RemovePermissionOverwriteAsync(role); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user) - => await RemovePermissionOverwriteAsync(user); + async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) + => await AddPermissionOverwriteAsync(role, permissions, options); + async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) + => await AddPermissionOverwriteAsync(user, permissions, options); + async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) + => await RemovePermissionOverwriteAsync(role, options); + async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) + => await RemovePermissionOverwriteAsync(user, options); - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); //IChannel - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? - Task IChannel.GetUserAsync(ulong id, CacheMode mode) + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); //Overriden in Text/Voice //TODO: Does this actually override? } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 2e894afde..079ddfd10 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -44,46 +44,46 @@ namespace Discord.WebSocket Topic = model.Topic.Value; } - public Task ModifyAsync(Action func) - => ChannelHelper.ModifyAsync(this, Discord, func); + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); //Messages public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); - public async Task GetMessageAsync(ulong 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); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); return msg; } - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload); + 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() - => ChannelHelper.GetPinnedMessagesAsync(this, Discord); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS); - public Task SendFileAsync(string filePath, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS); + public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); - public Task DeleteMessagesAsync(IEnumerable messages) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages); + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); - public IDisposable EnterTypingState() - => ChannelHelper.EnterTypingState(this, Discord); + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) => _messages.Add(msg); @@ -108,34 +108,34 @@ namespace Discord.WebSocket internal new SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; //IGuildChannel - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //IMessageChannel - async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode) + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id); + return await GetMessageAsync(id, options); else return GetCachedMessage(id); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode); - async Task> IMessageChannel.GetPinnedMessagesAsync() - => await GetPinnedMessagesAsync().ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS) - => await SendFileAsync(filePath, text, isTTS); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS) - => await SendFileAsync(stream, filename, text, isTTS); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS) - => await SendMessageAsync(text, isTTS); - IDisposable IMessageChannel.EnterTypingState() - => EnterTypingState(); + 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, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); } } \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 62f46cf60..b311f3c01 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -38,8 +38,8 @@ namespace Discord.WebSocket UserLimit = model.UserLimit.Value; } - public Task ModifyAsync(Action func) - => ChannelHelper.ModifyAsync(this, Discord, func); + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); public override SocketGuildUser GetUser(ulong id) { @@ -56,9 +56,9 @@ namespace Discord.WebSocket Task IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } //IGuildChannel - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode) + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode) + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 3ac802434..f5a24c536 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -241,34 +241,34 @@ namespace Discord.WebSocket } //General - public Task DeleteAsync() - => GuildHelper.DeleteAsync(this, Discord); + public Task DeleteAsync(RequestOptions options) + => GuildHelper.DeleteAsync(this, Discord, options); - public Task ModifyAsync(Action func) - => GuildHelper.ModifyAsync(this, Discord, func); - public Task ModifyEmbedAsync(Action func) - => GuildHelper.ModifyEmbedAsync(this, Discord, func); - public Task ModifyChannelsAsync(IEnumerable args) - => GuildHelper.ModifyChannelsAsync(this, Discord, args); - public Task ModifyRolesAsync(IEnumerable args) - => GuildHelper.ModifyRolesAsync(this, Discord, args); + 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 ModifyChannelsAsync(IEnumerable args, RequestOptions options = null) + => GuildHelper.ModifyChannelsAsync(this, Discord, args, options); + public Task ModifyRolesAsync(IEnumerable args, RequestOptions options = null) + => GuildHelper.ModifyRolesAsync(this, Discord, args, options); - public Task LeaveAsync() - => GuildHelper.LeaveAsync(this, Discord); + public Task LeaveAsync(RequestOptions options = null) + => GuildHelper.LeaveAsync(this, Discord, options); //Bans - public Task> GetBansAsync() - => GuildHelper.GetBansAsync(this, Discord); + public Task> GetBansAsync(RequestOptions options = null) + => GuildHelper.GetBansAsync(this, Discord, options); - public Task AddBanAsync(IUser user, int pruneDays = 0) - => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays); - public Task AddBanAsync(ulong userId, int pruneDays = 0) - => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays); + public Task AddBanAsync(IUser user, int pruneDays = 0, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, options); + public Task AddBanAsync(ulong userId, int pruneDays = 0, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, options); - public Task RemoveBanAsync(IUser user) - => GuildHelper.RemoveBanAsync(this, Discord, user.Id); - public Task RemoveBanAsync(ulong userId) - => GuildHelper.RemoveBanAsync(this, Discord, userId); + 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) @@ -278,10 +278,10 @@ namespace Discord.WebSocket return channel; return null; } - public Task CreateTextChannelAsync(string name) - => GuildHelper.CreateTextChannelAsync(this, Discord, name); - public Task CreateVoiceChannelAsync(string name) - => GuildHelper.CreateVoiceChannelAsync(this, Discord, name); + public Task CreateTextChannelAsync(string name, RequestOptions options = null) + => GuildHelper.CreateTextChannelAsync(this, Discord, name, options); + public Task CreateVoiceChannelAsync(string name, RequestOptions options = null) + => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) { var channel = SocketGuildChannel.Create(this, state, model); @@ -297,14 +297,14 @@ namespace Discord.WebSocket } //Integrations - public Task> GetIntegrationsAsync() - => GuildHelper.GetIntegrationsAsync(this, Discord); - public Task CreateIntegrationAsync(ulong id, string type) - => GuildHelper.CreateIntegrationAsync(this, Discord, id, type); + 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() - => GuildHelper.GetInvitesAsync(this, Discord); + public Task> GetInvitesAsync(RequestOptions options = null) + => GuildHelper.GetInvitesAsync(this, Discord, options); //Roles public SocketRole GetRole(ulong id) @@ -314,8 +314,9 @@ namespace Discord.WebSocket return value; return null; } - public Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false) - => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted); + 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); @@ -345,8 +346,8 @@ namespace Discord.WebSocket return member; return null; } - public Task PruneUsersAsync(int days = 30, bool simulate = false) - => GuildHelper.PruneUsersAsync(this, Discord, days, simulate); + public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) + => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); internal SocketGuildUser AddOrUpdateUser(MemberModel model) { @@ -532,36 +533,36 @@ namespace Discord.WebSocket IRole IGuild.EveryoneRole => EveryoneRole; IReadOnlyCollection IGuild.Roles => Roles; - async Task> IGuild.GetBansAsync() - => await GetBansAsync(); + async Task> IGuild.GetBansAsync(RequestOptions options) + => await GetBansAsync(options); - Task> IGuild.GetChannelsAsync(CacheMode mode) + Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Channels); - Task IGuild.GetChannelAsync(ulong id, CacheMode mode) + Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetChannel(id)); - async Task IGuild.CreateTextChannelAsync(string name) - => await CreateTextChannelAsync(name); - async Task IGuild.CreateVoiceChannelAsync(string name) - => await CreateVoiceChannelAsync(name); + async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options) + => await CreateTextChannelAsync(name, options); + async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) + => await CreateVoiceChannelAsync(name, options); - async Task> IGuild.GetIntegrationsAsync() - => await GetIntegrationsAsync(); - async Task IGuild.CreateIntegrationAsync(ulong id, string type) - => await CreateIntegrationAsync(id, type); + async Task> IGuild.GetIntegrationsAsync(RequestOptions options) + => await GetIntegrationsAsync(options); + async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) + => await CreateIntegrationAsync(id, type, options); - async Task> IGuild.GetInvitesAsync() - => await GetInvitesAsync(); + async Task> IGuild.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options); IRole IGuild.GetRole(ulong id) => GetRole(id); - async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted) - => await CreateRoleAsync(name, permissions, color, isHoisted); + async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) + => await CreateRoleAsync(name, permissions, color, isHoisted, options); - Task> IGuild.GetUsersAsync(CacheMode mode) + Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Users); - Task IGuild.GetUserAsync(ulong id, CacheMode mode) + Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); - Task IGuild.GetCurrentUserAsync(CacheMode mode) + Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) => Task.FromResult(GetCurrentUser()); Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 683f6ec3d..cb88f0430 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -112,15 +112,15 @@ namespace Discord.WebSocket } } - public Task ModifyAsync(Action func) - => MessageHelper.ModifyAsync(this, Discord, func); - public Task DeleteAsync() - => MessageHelper.DeleteAsync(this, Discord); - - public Task PinAsync() - => MessageHelper.PinAsync(this, Discord); - public Task UnpinAsync() - => MessageHelper.UnpinAsync(this, Discord); + public Task ModifyAsync(Action func, RequestOptions options = null) + => MessageHelper.ModifyAsync(this, Discord, func, options); + public Task DeleteAsync(RequestOptions options = null) + => MessageHelper.DeleteAsync(this, 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(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index 1cb2e2812..7a1f0b3bb 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -43,10 +43,10 @@ namespace Discord.WebSocket Permissions = new GuildPermissions(model.Permissions); } - public Task ModifyAsync(Action func) - => RoleHelper.ModifyAsync(this, Discord, func); - public Task DeleteAsync() - => RoleHelper.DeleteAsync(this, Discord); + public Task ModifyAsync(Action func, RequestOptions options) + => RoleHelper.ModifyAsync(this, Discord, func, options); + public Task DeleteAsync(RequestOptions options) + => RoleHelper.DeleteAsync(this, Discord, options); public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 6573e6d0d..00972f51a 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -82,10 +82,10 @@ namespace Discord.WebSocket _roleIds = roles.ToImmutable(); } - public Task ModifyAsync(Action func) - => UserHelper.ModifyAsync(this, Discord, func); - public Task KickAsync() - => UserHelper.KickAsync(this, Discord); + public Task ModifyAsync(Action func, RequestOptions options = null) + => UserHelper.ModifyAsync(this, Discord, func, options); + public Task KickAsync(RequestOptions options = null) + => UserHelper.KickAsync(this, Discord, options); public ChannelPermissions GetPermissions(IGuildChannel channel) => new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, GuildPermissions.RawValue)); @@ -97,7 +97,7 @@ namespace Discord.WebSocket IReadOnlyCollection IGuildUser.RoleIds => RoleIds; //IUser - Task IUser.GetDMChannelAsync(CacheMode mode) + Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(GlobalUser.DMChannel); //IVoiceState diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs index 8dfae8d49..629aa2093 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -7,17 +7,17 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct SocketPresence : IPresence { - public Game? Game { get; } public UserStatus Status { get; } + public Game? Game { get; } - internal SocketPresence(Game? game, UserStatus status) + internal SocketPresence(UserStatus status, Game? game) { - Game = game; Status = status; + Game = game; } internal static SocketPresence Create(Model model) { - return new SocketPresence(model.Game != null ? Discord.Game.Create(model.Game) : (Game?)null, model.Status); + return new SocketPresence(model.Status, model.Game != null ? Discord.Game.Create(model.Game) : (Game?)null); } public override string ToString() => Status.ToString(); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 574b508ea..76af76022 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -40,17 +40,17 @@ namespace Discord.WebSocket Presence = SocketPresence.Create(model); } - public Task CreateDMChannelAsync() - => UserHelper.CreateDMChannelAsync(this, Discord); + public Task CreateDMChannelAsync(RequestOptions options = null) + => UserHelper.CreateDMChannelAsync(this, Discord, options); public override string ToString() => $"{Username}#{Discriminator}"; internal string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; internal SocketUser Clone() => MemberwiseClone() as SocketUser; //IUser - Task IUser.GetDMChannelAsync(CacheMode mode) + Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(GlobalUser.DMChannel); - async Task IUser.CreateDMChannelAsync() - => await CreateDMChannelAsync(); + async Task IUser.CreateDMChannelAsync(RequestOptions options) + => await CreateDMChannelAsync(options); } } From 6c34441f4ceb1d8b0712245a9058c33df731988e Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 03:16:14 -0300 Subject: [PATCH 042/102] Added SelfUser.ModifyStatusAsync --- .../API/Rest/ModifyPresenceParams.cs | 2 + .../Entities/Users/UserStatus.cs | 1 + .../Net/Converters/UserStatusConverter.cs | 1 + .../API/DiscordSocketApiClient.cs | 6 +- .../API/Gateway/StatusUpdateParams.cs | 6 +- .../DiscordSocketClient.cs | 4 +- .../Entities/Users/SocketSelfUser.cs | 58 +++++++++++++++++-- .../Entities/Users/SocketSimpleUser.cs | 2 +- 8 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/Discord.Net.Core/API/Rest/ModifyPresenceParams.cs b/src/Discord.Net.Core/API/Rest/ModifyPresenceParams.cs index 52145643a..ac55e2491 100644 --- a/src/Discord.Net.Core/API/Rest/ModifyPresenceParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyPresenceParams.cs @@ -1,4 +1,6 @@ #pragma warning disable CS1591 +using System; + namespace Discord.API.Rest { public class ModifyPresenceParams diff --git a/src/Discord.Net.Core/Entities/Users/UserStatus.cs b/src/Discord.Net.Core/Entities/Users/UserStatus.cs index ea2c3680e..d183c139d 100644 --- a/src/Discord.Net.Core/Entities/Users/UserStatus.cs +++ b/src/Discord.Net.Core/Entities/Users/UserStatus.cs @@ -5,6 +5,7 @@ Unknown, Online, Idle, + AFK, DoNotDisturb, Invisible, Offline diff --git a/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs b/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs index 3b4e1c0cb..cbcc9eab2 100644 --- a/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs @@ -38,6 +38,7 @@ namespace Discord.Net.Converters writer.WriteValue("online"); break; case UserStatus.Idle: + case UserStatus.AFK: writer.WriteValue("idle"); break; case UserStatus.DoNotDisturb: diff --git a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs index cccda4290..5aea2dcb4 100644 --- a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs @@ -212,12 +212,14 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false); } - public async Task SendStatusUpdateAsync(long? idleSince, 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 { - IdleSince = idleSince, + Status = status, + IdleSince = since, + IsAFK = isAFK, Game = game }; await SendGatewayAsync(GatewayOpCode.StatusUpdate, args, options: options).ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs index 26b726b36..ae1f79283 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs @@ -6,8 +6,12 @@ namespace Discord.API.Gateway [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public class StatusUpdateParams { - [JsonProperty("idle_since"), Int53] + [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/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 4f3960623..6c59c4b0b 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -376,7 +376,7 @@ namespace Discord.WebSocket { var user = SocketGlobalUser.Create(this, state, model); user.GlobalUser.AddRef(); - user.Presence = new SocketPresence(null, UserStatus.Online); + user.Presence = new SocketPresence(UserStatus.Online, null); return user; }); } @@ -1311,7 +1311,7 @@ namespace Discord.WebSocket } else { - before = new SocketPresence(null, UserStatus.Offline); + before = new SocketPresence(UserStatus.Offline, null); user = guild.AddOrUpdateUser(data); } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index 21578de41..926dca5cc 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -3,6 +3,7 @@ using Discord.Rest; using System; using System.Diagnostics; using System.Threading.Tasks; +using GameEntity = Discord.Game; using Model = Discord.API.User; namespace Discord.WebSocket @@ -10,6 +11,8 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketSelfUser : SocketUser, ISelfUser { + private DateTimeOffset? _statusSince; + public string Email { get; private set; } public bool IsVerified { get; private set; } public bool IsMfaEnabled { get; private set; } @@ -44,12 +47,57 @@ namespace Discord.WebSocket IsMfaEnabled = model.MfaEnabled.Value; } - public Task ModifyAsync(Action func) - => UserHelper.ModifyAsync(this, Discord, func); + public Task ModifyAsync(Action func, RequestOptions options = null) + => UserHelper.ModifyAsync(this, Discord, func, options); + public async Task ModifyStatusAsync(Action func, RequestOptions options = null) + { + var args = new ModifyPresenceParams(); + func(args); - internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; + UserStatus status; + if (args.Status.IsSpecified) + { + status = args.Status.Value; + if (status == UserStatus.AFK) + _statusSince = DateTimeOffset.UtcNow; + else + _statusSince = null; + } + else + status = Status; - //ISelfUser - Task ISelfUser.ModifyStatusAsync(Action func) { throw new NotSupportedException(); } + GameEntity? game; + if (args.Game.IsSpecified) + { + var model = args.Game.Value; + if (model != null) + game = GameEntity.Create(model); + else + game = null; + } + else + game = Game; + + Presence = new SocketPresence(status, game); + + await SendStatus(status, game); + } + internal async Task SendStatus(UserStatus status, GameEntity? game) + { + var gameModel = game != null ? new API.Game + { + Name = game.Value.Name, + StreamType = game.Value.StreamType, + StreamUrl = game.Value.StreamUrl + } : null; + + await Discord.ApiClient.SendStatusUpdateAsync( + status, + status == UserStatus.AFK, + _statusSince != null ? _statusSince.Value.ToUnixTimeMilliseconds() : (long?)null, + gameModel); + } + + internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs index a45a68703..1ecb5e578 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSimpleUser.cs @@ -12,7 +12,7 @@ namespace Discord.WebSocket public override string Username { get; internal set; } public override ushort DiscriminatorValue { get; internal set; } public override string AvatarId { get; internal set; } - internal override SocketPresence Presence { get { return new SocketPresence(null, UserStatus.Offline); } set { } } + internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } From 8c5990d575d4a9f9b04f214407c0a869089cb6b2 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 03:41:30 -0300 Subject: [PATCH 043/102] Fixed guilds not completing sync on user tokens --- .../Entities/Guilds/SocketGuild.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index f5a24c536..54128d278 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -5,6 +5,7 @@ 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; @@ -29,7 +30,6 @@ namespace Discord.WebSocket private ConcurrentDictionary _members; private ConcurrentDictionary _roles; private ConcurrentDictionary _voiceStates; - private ConcurrentDictionary _cachedPresences; private ImmutableArray _emojis; private ImmutableArray _features; internal bool _available; @@ -127,20 +127,17 @@ namespace Discord.WebSocket members.TryAdd(member.Id, member); } DownloadedMemberCount = members.Count; - } - var cachedPresences = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Presences.Length * 1.05)); - { + for (int i = 0; i < model.Presences.Length; i++) { SocketGuildUser member; - if (_members.TryGetValue(model.Presences[i].User.Id, out member)) + if (members.TryGetValue(model.Presences[i].User.Id, out member)) member.Update(state, model.Presences[i]); else - cachedPresences.TryAdd(model.Presences[i].User.Id, model.Presences[i]); + Debug.Assert(false); } } _members = members; - _cachedPresences = cachedPresences; MemberCount = model.MemberCount; var voiceStates = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.VoiceStates.Length * 1.05)); @@ -216,20 +213,21 @@ namespace Discord.WebSocket members.TryAdd(member.Id, member); } DownloadedMemberCount = members.Count; - } - var cachedPresences = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Presences.Length * 1.05)); - { + for (int i = 0; i < model.Presences.Length; i++) { SocketGuildUser member; - if (_members.TryGetValue(model.Presences[i].User.Id, out member)) + if (members.TryGetValue(model.Presences[i].User.Id, out member)) member.Update(state, model.Presences[i]); else - cachedPresences.TryAdd(model.Presences[i].User.Id, model.Presences[i]); + Debug.Assert(false); } } _members = members; - _cachedPresences = cachedPresences; + + var _ = _syncPromise.TrySetResultAsync(true); + if (!model.Large) + _ = _downloaderPromise.TrySetResultAsync(true); } internal void Update(ClientState state, EmojiUpdateModel model) From 6c77914162ba3b16346fda5daeb1e657b9339412 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 03:44:09 -0300 Subject: [PATCH 044/102] Fixed a few non-optional RequestOptions --- src/Discord.Net.Rest/Entities/Messages/RestMessage.cs | 2 +- src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 4 ++-- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 2 +- src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 7096d6198..7b48cf771 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -48,7 +48,7 @@ namespace Discord.Rest Content = model.Content.Value; } - public async Task UpdateAsync(RequestOptions options) + public async Task UpdateAsync(RequestOptions options = null) { var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id, options).ConfigureAwait(false); Update(model); diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index e8aaaa772..2daf66ec8 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -40,9 +40,9 @@ namespace Discord.Rest Permissions = new GuildPermissions(model.Permissions); } - public Task ModifyAsync(Action func, RequestOptions options) + public Task ModifyAsync(Action func, RequestOptions options = null) => RoleHelper.ModifyAsync(this, Discord, func, options); - public Task DeleteAsync(RequestOptions options) + public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); public override string ToString() => Name; diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 54128d278..a75a4b8ce 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -239,7 +239,7 @@ namespace Discord.WebSocket } //General - public Task DeleteAsync(RequestOptions options) + public Task DeleteAsync(RequestOptions options = null) => GuildHelper.DeleteAsync(this, Discord, options); public Task ModifyAsync(Action func, RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index 7a1f0b3bb..b5b83a4f4 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -43,9 +43,9 @@ namespace Discord.WebSocket Permissions = new GuildPermissions(model.Permissions); } - public Task ModifyAsync(Action func, RequestOptions options) + public Task ModifyAsync(Action func, RequestOptions options = null) => RoleHelper.ModifyAsync(this, Discord, func, options); - public Task DeleteAsync(RequestOptions options) + public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); public override string ToString() => Name; From 98475302afc887fb94d448535b4f897d73310d3f Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 04:05:32 -0300 Subject: [PATCH 045/102] Added bool/char typereaders --- src/Discord.Net.Commands/CommandService.cs | 2 ++ src/Discord.Net.Commands/PrimitiveParsers.cs | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index b610fa4b7..9b77b65e6 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -26,6 +26,8 @@ namespace Discord.Commands _map = new CommandMap(); _typeReaders = new ConcurrentDictionary { + [typeof(bool)] = new SimpleTypeReader(), + [typeof(char)] = new SimpleTypeReader(), [typeof(string)] = new SimpleTypeReader(), [typeof(byte)] = new SimpleTypeReader(), [typeof(sbyte)] = new SimpleTypeReader(), diff --git a/src/Discord.Net.Commands/PrimitiveParsers.cs b/src/Discord.Net.Commands/PrimitiveParsers.cs index ac705764e..5e3dcd68a 100644 --- a/src/Discord.Net.Commands/PrimitiveParsers.cs +++ b/src/Discord.Net.Commands/PrimitiveParsers.cs @@ -13,7 +13,7 @@ namespace Discord.Commands static PrimitiveParsers() { var parserBuilder = ImmutableDictionary.CreateBuilder(); - parserBuilder[typeof(string)] = (TryParseDelegate)delegate(string str, out string value) { value = str; return true; }; + parserBuilder[typeof(bool)] = (TryParseDelegate)bool.TryParse; parserBuilder[typeof(sbyte)] = (TryParseDelegate)sbyte.TryParse; parserBuilder[typeof(byte)] = (TryParseDelegate)byte.TryParse; parserBuilder[typeof(short)] = (TryParseDelegate)short.TryParse; @@ -27,6 +27,12 @@ namespace Discord.Commands parserBuilder[typeof(decimal)] = (TryParseDelegate)decimal.TryParse; parserBuilder[typeof(DateTime)] = (TryParseDelegate)DateTime.TryParse; parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate)DateTimeOffset.TryParse; + parserBuilder[typeof(char)] = (TryParseDelegate)char.TryParse; + parserBuilder[typeof(string)] = (TryParseDelegate)delegate (string str, out string value) + { + value = str; + return true; + }; _parsers = parserBuilder.ToImmutable(); } From 472412e232b219966a045d03863aeba1ffd6d133 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 04:24:24 -0300 Subject: [PATCH 046/102] Readded IMessageChannel.TriggerTypingAsync --- src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs | 4 +++- .../Extensions/SnowflakeEntityExtensions.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 7 ++++++- src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs | 2 ++ .../Entities/Channels/RestGroupChannel.cs | 2 ++ src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 2 ++ src/Discord.Net.Rest/Utils/TypingNotifier.cs | 8 ++++---- .../Entities/Channels/SocketChannelHelper.cs | 4 ++-- .../Entities/Channels/SocketDMChannel.cs | 2 ++ .../Entities/Channels/SocketGroupChannel.cs | 2 ++ .../Entities/Channels/SocketTextChannel.cs | 2 ++ 11 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 77ed811eb..7c13e4a6f 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -30,7 +30,9 @@ namespace Discord /// Bulk deletes multiple messages. Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); - /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. + /// 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. IDisposable EnterTypingState(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs b/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs index adbf31a7e..271ad9e00 100644 --- a/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs +++ b/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs @@ -4,7 +4,7 @@ namespace Discord.Extensions { public static class SnowflakeEntityExtensions { - //TODO: C#7 Candidate for Extension Property. + //TODO: C#7 Candidate for extension property. public static DateTimeOffset GetCreatedAt(this ISnowflakeEntity entity) => DateTimeUtils.FromSnowflake(entity.Id); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 319520386..a6c172918 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -204,7 +204,12 @@ namespace Discord.Rest } //Typing - public static IDisposable EnterTypingState(IChannel channel, BaseDiscordClient client, + public static async Task TriggerTypingAsync(IMessageChannel channel, BaseDiscordClient client, + RequestOptions options = null) + { + await client.ApiClient.TriggerTypingIndicatorAsync(channel.Id, options); + } + public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, RequestOptions options) => new TypingNotifier(client, channel, options); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 3adbd3fa4..9732c65e8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -73,6 +73,8 @@ namespace Discord.Rest public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + public Task TriggerTypingAsync(RequestOptions options = null) + => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 4a1ea7b87..5e9c0dcbf 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -86,6 +86,8 @@ namespace Discord.Rest public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + public Task TriggerTypingAsync(RequestOptions options = null) + => ChannelHelper.TriggerTypingAsync(this, Discord, options); 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 b90fff58f..e8c82fcb5 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -63,6 +63,8 @@ namespace Discord.Rest public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + public Task TriggerTypingAsync(RequestOptions options = null) + => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); diff --git a/src/Discord.Net.Rest/Utils/TypingNotifier.cs b/src/Discord.Net.Rest/Utils/TypingNotifier.cs index 62d19b5a1..433553e00 100644 --- a/src/Discord.Net.Rest/Utils/TypingNotifier.cs +++ b/src/Discord.Net.Rest/Utils/TypingNotifier.cs @@ -8,14 +8,14 @@ namespace Discord.Rest { private readonly BaseDiscordClient _client; private readonly CancellationTokenSource _cancelToken; - private readonly ulong _channelId; + private readonly IMessageChannel _channel; private readonly RequestOptions _options; - public TypingNotifier(BaseDiscordClient discord, IChannel channel, RequestOptions options) + public TypingNotifier(BaseDiscordClient discord, IMessageChannel channel, RequestOptions options) { _client = discord; _cancelToken = new CancellationTokenSource(); - _channelId = channel.Id; + _channel = channel; _options = options; var _ = Run(); } @@ -29,7 +29,7 @@ namespace Discord.Rest { try { - await _client.ApiClient.TriggerTypingIndicatorAsync(_channelId); + await _channel.TriggerTypingAsync(_options); } catch { } await Task.Delay(9750, token); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 887499c59..06cdf6d09 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -43,7 +43,7 @@ namespace Discord.WebSocket public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, SocketMessage msg) { - //C#7 Candidate for pattern matching + //TODO: C#7 Candidate for pattern matching if (channel is SocketDMChannel) (channel as SocketDMChannel).AddMessage(msg); else if (channel is SocketGroupChannel) @@ -56,7 +56,7 @@ namespace Discord.WebSocket public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord, ulong id) { - //C#7 Candidate for pattern matching + //TODO: C#7 Candidate for pattern matching if (channel is SocketDMChannel) return (channel as SocketDMChannel).RemoveMessage(id); else if (channel is SocketGroupChannel) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 375dce605..00849537c 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -76,6 +76,8 @@ namespace Discord.WebSocket public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + public Task TriggerTypingAsync(RequestOptions options = null) + => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 732296ff0..cc75139a1 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -99,6 +99,8 @@ namespace Discord.WebSocket public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + public Task TriggerTypingAsync(RequestOptions options = null) + => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 079ddfd10..4f84e6ee4 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -82,6 +82,8 @@ namespace Discord.WebSocket public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); + public Task TriggerTypingAsync(RequestOptions options = null) + => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); From 5415b8f8f6505b0acacfaaea1e58690fe8b74e35 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 04:28:54 -0300 Subject: [PATCH 047/102] Renamed Emoji -> GuildEmoji --- .../Entities/Guilds/{Emoji.cs => GuildEmoji.cs} | 8 ++++---- src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 2 +- .../Entities/Guilds/RestGuild.cs | 10 +++++----- .../Entities/Guilds/SocketGuild.cs | 16 ++++++++-------- 4 files changed, 18 insertions(+), 18 deletions(-) rename src/Discord.Net.Core/Entities/Guilds/{Emoji.cs => GuildEmoji.cs} (68%) diff --git a/src/Discord.Net.Core/Entities/Guilds/Emoji.cs b/src/Discord.Net.Core/Entities/Guilds/GuildEmoji.cs similarity index 68% rename from src/Discord.Net.Core/Entities/Guilds/Emoji.cs rename to src/Discord.Net.Core/Entities/Guilds/GuildEmoji.cs index ea9d176df..8b2bbd9c2 100644 --- a/src/Discord.Net.Core/Entities/Guilds/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildEmoji.cs @@ -6,7 +6,7 @@ using Model = Discord.API.Emoji; namespace Discord { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public struct Emoji + public struct GuildEmoji { public ulong Id { get; } public string Name { get; } @@ -14,7 +14,7 @@ namespace Discord public bool RequireColons { get; } public IReadOnlyList RoleIds { get; } - private Emoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList roleIds) + private GuildEmoji(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList roleIds) { Id = id; Name = name; @@ -22,9 +22,9 @@ namespace Discord RequireColons = requireColons; RoleIds = roleIds; } - internal static Emoji Create(Model model) + internal static GuildEmoji Create(Model model) { - return new Emoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); + return new GuildEmoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); } public override string ToString() => Name; diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 2ff7afa3f..413b5da62 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -46,7 +46,7 @@ namespace Discord /// 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 Emojis { get; } + IReadOnlyCollection Emojis { get; } /// Gets a collection of all extra features added to this guild. IReadOnlyCollection Features { get; } /// Gets a collection of all roles in this guild. diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index a51f60d5a..188148eee 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -13,7 +13,7 @@ namespace Discord.Rest public class RestGuild : RestEntity, IGuild, IUpdateable { private ImmutableDictionary _roles; - private ImmutableArray _emojis; + private ImmutableArray _emojis; private ImmutableArray _features; public string Name { get; private set; } @@ -37,7 +37,7 @@ namespace Discord.Rest public RestRole EveryoneRole => GetRole(Id); public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); - public IReadOnlyCollection Emojis => _emojis; + public IReadOnlyCollection Emojis => _emojis; public IReadOnlyCollection Features => _features; internal RestGuild(BaseDiscordClient client, ulong id) @@ -67,13 +67,13 @@ namespace Discord.Rest if (model.Emojis != null) { - var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); + var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); for (int i = 0; i < model.Emojis.Length; i++) - emojis.Add(Emoji.Create(model.Emojis[i])); + emojis.Add(GuildEmoji.Create(model.Emojis[i])); _emojis = emojis.ToImmutableArray(); } else - _emojis = ImmutableArray.Create(); + _emojis = ImmutableArray.Create(); if (model.Features != null) _features = model.Features.ToImmutableArray(); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index a75a4b8ce..8d81a961d 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -30,7 +30,7 @@ namespace Discord.WebSocket private ConcurrentDictionary _members; private ConcurrentDictionary _roles; private ConcurrentDictionary _voiceStates; - private ImmutableArray _emojis; + private ImmutableArray _emojis; private ImmutableArray _features; internal bool _available; @@ -69,7 +69,7 @@ namespace Discord.WebSocket return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); } } - public IReadOnlyCollection Emojis => _emojis; + public IReadOnlyCollection Emojis => _emojis; public IReadOnlyCollection Features => _features; public IReadOnlyCollection Users => _members.ToReadOnlyCollection(); public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); @@ -79,7 +79,7 @@ namespace Discord.WebSocket : base(client, id) { _audioLock = new SemaphoreSlim(1, 1); - _emojis = ImmutableArray.Create(); + _emojis = ImmutableArray.Create(); _features = ImmutableArray.Create(); } internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model) @@ -179,13 +179,13 @@ namespace Discord.WebSocket if (model.Emojis != null) { - var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); + var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); for (int i = 0; i < model.Emojis.Length; i++) - emojis.Add(Emoji.Create(model.Emojis[i])); + emojis.Add(GuildEmoji.Create(model.Emojis[i])); _emojis = emojis.ToImmutable(); } else - _emojis = ImmutableArray.Create(); + _emojis = ImmutableArray.Create(); if (model.Features != null) _features = model.Features.ToImmutableArray(); @@ -232,9 +232,9 @@ namespace Discord.WebSocket internal void Update(ClientState state, EmojiUpdateModel model) { - var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); + var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); for (int i = 0; i < model.Emojis.Length; i++) - emojis.Add(Emoji.Create(model.Emojis[i])); + emojis.Add(GuildEmoji.Create(model.Emojis[i])); _emojis = emojis.ToImmutable(); } From bb1fc2c42a49d92aa7076bb905ee8f12adeaa842 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 05:06:25 -0300 Subject: [PATCH 048/102] Fixed MessageCache GetMany with Before direction --- src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index 70110387f..7b8d9c2cd 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -61,8 +61,10 @@ namespace Discord.WebSocket else cachedMessageIds = _orderedMessages.Where(x => x > fromMessageId.Value); + if (dir == Direction.Before) + cachedMessageIds = cachedMessageIds.Reverse(); + return cachedMessageIds - .Take(limit) .Select(x => { SocketMessage msg; @@ -71,6 +73,7 @@ namespace Discord.WebSocket return null; }) .Where(x => x != null) + .Take(limit) .ToImmutableArray(); } } From 0b47d5bd2ed547efb41c061ae98e017682f5d328 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 05:37:45 -0300 Subject: [PATCH 049/102] Improved GetMessages with cache enabled --- .../Entities/Channels/SocketChannelHelper.cs | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 06cdf6d09..5c02d457f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -11,25 +11,42 @@ namespace Discord.WebSocket public static IAsyncEnumerable> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, ulong? fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { - IReadOnlyCollection cachedMessages; - IAsyncEnumerable> result; + if (dir == Direction.Around) + throw new NotImplementedException(); //TODO: Impl - if (messages != null) //Cache enabled - cachedMessages = messages.GetMany(fromMessageId, dir, limit); - else - cachedMessages = ImmutableArray.Create(); - - result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>(); - limit -= cachedMessages.Count; - if (mode == CacheMode.CacheOnly || limit <= 0) - return result; + 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(); + result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>(); + } + if (dir == Direction.Before) - fromMessageId = cachedMessages.Min(x => x.Id); + { + limit -= cachedMessages.Count; + if (mode == CacheMode.CacheOnly || limit <= 0) + return result; + + //Download remaining messages + var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, cachedMessages.Min(x => x.Id), dir, limit, options); + return result.Concat(downloadedMessages); + } else - fromMessageId = cachedMessages.Max(x => x.Id); - var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options); - return result.Concat(downloadedMessages); + { + if (mode == CacheMode.CacheOnly) + return result; + + //Dont use cache in this case + return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options); + } } public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, ulong? fromMessageId, Direction dir, int limit) From 9e982ccd4a9dac22659a222e4abe9cc6f831b81e Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 05:37:57 -0300 Subject: [PATCH 050/102] Added IMessage.Emojis --- src/Discord.Net.Core/API/CDN.cs | 2 + .../Entities/Messages/Emoji.cs | 20 ++++++++ .../Entities/Messages/IMessage.cs | 2 + .../{RestAttachment.cs => Attachment.cs} | 8 ++-- .../Messages/{RestEmbed.cs => Embed.cs} | 8 ++-- .../Entities/Messages/MessageHelper.cs | 20 ++++++++ .../Entities/Messages/RestMessage.cs | 13 +++-- .../Entities/Messages/RestUserMessage.cs | 27 ++++++----- .../Entities/Messages/SocketMessage.cs | 18 ++++--- .../Entities/Messages/SocketUserMessage.cs | 48 +++++++++++-------- 10 files changed, 115 insertions(+), 51 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/Emoji.cs rename src/Discord.Net.Rest/Entities/Messages/{RestAttachment.cs => Attachment.cs} (73%) rename src/Discord.Net.Rest/Entities/Messages/{RestEmbed.cs => Embed.cs} (75%) diff --git a/src/Discord.Net.Core/API/CDN.cs b/src/Discord.Net.Core/API/CDN.cs index d7a3f1d39..e4fcbc8c4 100644 --- a/src/Discord.Net.Core/API/CDN.cs +++ b/src/Discord.Net.Core/API/CDN.cs @@ -12,5 +12,7 @@ => 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) + => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.png"; } } diff --git a/src/Discord.Net.Core/Entities/Messages/Emoji.cs b/src/Discord.Net.Core/Entities/Messages/Emoji.cs new file mode 100644 index 000000000..0fdbc69e2 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/Emoji.cs @@ -0,0 +1,20 @@ +using Discord.API; + +namespace Discord +{ + public struct Emoji + { + public ulong Id { get; } + public string Name { get; } + public int Index { get; } + + public string Url => CDN.GetEmojiUrl(Id); + + internal Emoji(ulong id, string name, int index) + { + Id = id; + Name = name; + Index = index; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index be83e7482..6291c1a3d 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -27,6 +27,8 @@ namespace Discord IReadOnlyCollection Attachments { get; } /// Returns a collection of all embeds included in this message. IReadOnlyCollection Embeds { get; } + /// Returns a collection of all custom emoji included in this message. + IReadOnlyCollection Emojis { get; } /// Returns a collection of channel ids mentioned in this message. IReadOnlyCollection MentionedChannelIds { get; } /// Returns a collection of roles mentioned in this message. diff --git a/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs similarity index 73% rename from src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs rename to src/Discord.Net.Rest/Entities/Messages/Attachment.cs index 5d31558c8..e185234ac 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestAttachment.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs @@ -4,7 +4,7 @@ using Model = Discord.API.Attachment; namespace Discord { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestAttachment : IAttachment + public class Attachment : IAttachment { public ulong Id { get; } public string Filename { get; } @@ -14,7 +14,7 @@ namespace Discord public int? Height { get; } public int? Width { get; } - internal RestAttachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width) + internal Attachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width) { Id = id; Filename = filename; @@ -24,9 +24,9 @@ namespace Discord Height = height; Width = width; } - internal static RestAttachment Create(Model model) + internal static Attachment Create(Model model) { - return new RestAttachment(model.Id, model.Filename, model.Url, model.ProxyUrl, model.Size, + 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); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs b/src/Discord.Net.Rest/Entities/Messages/Embed.cs similarity index 75% rename from src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs rename to src/Discord.Net.Rest/Entities/Messages/Embed.cs index 32856ebb6..20979534e 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestEmbed.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Embed.cs @@ -4,7 +4,7 @@ using Model = Discord.API.Embed; namespace Discord { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestEmbed : IEmbed + public class Embed : IEmbed { public string Description { get; } public string Url { get; } @@ -13,7 +13,7 @@ namespace Discord public EmbedProvider? Provider { get; } public EmbedThumbnail? Thumbnail { get; } - internal RestEmbed(string type, string title, string description, string url, EmbedProvider? provider, EmbedThumbnail? thumbnail) + internal Embed(string type, string title, string description, string url, EmbedProvider? provider, EmbedThumbnail? thumbnail) { Type = type; Title = title; @@ -22,9 +22,9 @@ namespace Discord Provider = provider; Thumbnail = thumbnail; } - internal static RestEmbed Create(Model model) + internal static Embed Create(Model model) { - return new RestEmbed(model.Type, model.Title, model.Description, model.Url, + return new Embed(model.Type, model.Title, model.Description, model.Url, model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null); } diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 6918dfb27..085f73741 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -1,11 +1,18 @@ using Discord.API.Rest; using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Discord.Rest { internal static class MessageHelper { + private static readonly Regex _emojiRegex = new Regex(@"<:(.+?):(\d+?)>", RegexOptions.Compiled); + public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, RequestOptions options) { @@ -29,5 +36,18 @@ namespace Discord.Rest { await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id, options); } + + public static ImmutableArray GetEmojis(string text) + { + var matches = _emojiRegex.Matches(text); + var builder = ImmutableArray.CreateBuilder(matches.Count); + foreach (var match in matches.OfType()) + { + ulong id; + if (ulong.TryParse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + builder.Add(new Emoji(id, match.Groups[1].Value, match.Index)); + } + return builder.ToImmutable(); + } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 7b48cf771..3ef933afb 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -19,11 +19,12 @@ namespace Discord.Rest public virtual bool IsPinned => false; public virtual bool IsWebhook => false; public virtual DateTimeOffset? EditedTimestamp => null; - public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); - public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); + public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + public virtual IReadOnlyCollection Emojis => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); @@ -57,5 +58,9 @@ namespace Discord.Rest public override string ToString() => Content; MessageType IMessage.Type => MessageType.Default; + IReadOnlyCollection IMessage.Attachments => Attachments; + IReadOnlyCollection IMessage.Embeds => Embeds; + IReadOnlyCollection IMessage.MentionedRoles => MentionedRoles; + IReadOnlyCollection IMessage.MentionedUsers => MentionedUsers; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index b28b321bf..88855f33c 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -13,8 +13,9 @@ namespace Discord.Rest { private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; - private ImmutableArray _attachments; - private ImmutableArray _embeds; + private ImmutableArray _attachments; + private ImmutableArray _embeds; + private ImmutableArray _emojis; private ImmutableArray _mentionedChannelIds; private ImmutableArray _mentionedRoles; private ImmutableArray _mentionedUsers; @@ -25,11 +26,12 @@ namespace Discord.Rest public override bool IsPinned => _isPinned; public override bool IsWebhook => WebhookId != null; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); - public override IReadOnlyCollection Attachments => _attachments; - public override IReadOnlyCollection Embeds => _embeds; + public override IReadOnlyCollection Attachments => _attachments; + public override IReadOnlyCollection Embeds => _embeds; + public override IReadOnlyCollection Emojis => _emojis; public override IReadOnlyCollection MentionedChannelIds => _mentionedChannelIds; - public override IReadOnlyCollection MentionedRoles => _mentionedRoles; - public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + public override IReadOnlyCollection MentionedRoles => _mentionedRoles; + public override IReadOnlyCollection MentionedUsers => _mentionedUsers; internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId) : base(discord, id, channelId) @@ -62,13 +64,13 @@ namespace Discord.Rest var value = model.Attachments.Value; if (value.Length > 0) { - var attachments = ImmutableArray.CreateBuilder(value.Length); + var attachments = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - attachments.Add(RestAttachment.Create(value[i])); + attachments.Add(Attachment.Create(value[i])); _attachments = attachments.ToImmutable(); } else - _attachments = ImmutableArray.Create(); + _attachments = ImmutableArray.Create(); } if (model.Embeds.IsSpecified) @@ -76,13 +78,13 @@ namespace Discord.Rest var value = model.Embeds.Value; if (value.Length > 0) { - var embeds = ImmutableArray.CreateBuilder(value.Length); + var embeds = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - embeds.Add(RestEmbed.Create(value[i])); + embeds.Add(Embed.Create(value[i])); _embeds = embeds.ToImmutable(); } else - _embeds = ImmutableArray.Create(); + _embeds = ImmutableArray.Create(); } ImmutableArray mentions = ImmutableArray.Create(); @@ -105,6 +107,7 @@ namespace Discord.Rest _mentionedUsers = MentionUtils.GetUserMentions(text, null, mentions); _mentionedChannelIds = MentionUtils.GetChannelMentions(text, null); _mentionedRoles = MentionUtils.GetRoleMentions(text, null); + _emojis = MessageHelper.GetEmojis(text); model.Content = text; } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index a4418a2e8..9badd0e48 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Model = Discord.API.Message; namespace Discord.WebSocket @@ -19,11 +20,12 @@ namespace Discord.WebSocket public virtual bool IsWebhook => 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 MentionedRoles => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); + public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + public virtual IReadOnlyCollection Emojis => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedChannels => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); @@ -55,7 +57,11 @@ namespace Discord.WebSocket //IMessage IUser IMessage.Author => Author; MessageType IMessage.Type => MessageType.Default; - + IReadOnlyCollection IMessage.Attachments => Attachments; + IReadOnlyCollection IMessage.Embeds => Embeds; + IReadOnlyCollection IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); + IReadOnlyCollection IMessage.MentionedRoles => MentionedRoles; + IReadOnlyCollection IMessage.MentionedUsers => MentionedUsers; ulong IMessage.ChannelId => Channel.Id; } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index cb88f0430..8a5b9e34c 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Message; @@ -14,11 +15,12 @@ namespace Discord.WebSocket { private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; - private ImmutableArray _attachments; - private ImmutableArray _embeds; - private ImmutableArray _mentionedChannelIds; - private ImmutableArray _mentionedRoles; - private ImmutableArray _mentionedUsers; + private ImmutableArray _attachments; + private ImmutableArray _embeds; + private ImmutableArray _emojis; + private ImmutableArray _mentionedChannels; + private ImmutableArray _mentionedRoles; + private ImmutableArray _mentionedUsers; public ulong? WebhookId { get; private set; } @@ -27,11 +29,12 @@ namespace Discord.WebSocket public override bool IsWebhook => WebhookId != null; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); - public override IReadOnlyCollection Attachments => _attachments; - public override IReadOnlyCollection Embeds => _embeds; - public override IReadOnlyCollection MentionedChannelIds => _mentionedChannelIds; - public override IReadOnlyCollection MentionedRoles => _mentionedRoles; - public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + public override IReadOnlyCollection Attachments => _attachments; + public override IReadOnlyCollection Embeds => _embeds; + public override IReadOnlyCollection Emojis => _emojis; + public override IReadOnlyCollection MentionedChannels => _mentionedChannels; + public override IReadOnlyCollection MentionedRoles => _mentionedRoles; + public override IReadOnlyCollection MentionedUsers => _mentionedUsers; internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) : base(discord, id, channel, author) @@ -64,13 +67,13 @@ namespace Discord.WebSocket var value = model.Attachments.Value; if (value.Length > 0) { - var attachments = ImmutableArray.CreateBuilder(value.Length); + var attachments = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - attachments.Add(RestAttachment.Create(value[i])); + attachments.Add(Attachment.Create(value[i])); _attachments = attachments.ToImmutable(); } else - _attachments = ImmutableArray.Create(); + _attachments = ImmutableArray.Create(); } if (model.Embeds.IsSpecified) @@ -78,24 +81,24 @@ namespace Discord.WebSocket var value = model.Embeds.Value; if (value.Length > 0) { - var embeds = ImmutableArray.CreateBuilder(value.Length); + var embeds = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - embeds.Add(RestEmbed.Create(value[i])); + embeds.Add(Embed.Create(value[i])); _embeds = embeds.ToImmutable(); } else - _embeds = ImmutableArray.Create(); + _embeds = ImmutableArray.Create(); } - ImmutableArray mentions = ImmutableArray.Create(); + ImmutableArray mentions = ImmutableArray.Create(); if (model.Mentions.IsSpecified) { var value = model.Mentions.Value; if (value.Length > 0) { - var newMentions = ImmutableArray.CreateBuilder(value.Length); + var newMentions = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - newMentions.Add(RestUser.Create(Discord, value[i])); + newMentions.Add(SocketSimpleUser.Create(Discord, Discord.State, value[i])); mentions = newMentions.ToImmutable(); } } @@ -106,8 +109,11 @@ namespace Discord.WebSocket var guild = (Channel as SocketGuildChannel)?.Guild; _mentionedUsers = MentionUtils.GetUserMentions(text, Channel, mentions); - _mentionedChannelIds = MentionUtils.GetChannelMentions(text, guild); - _mentionedRoles = MentionUtils.GetRoleMentions(text, guild); + _mentionedChannels = MentionUtils.GetChannelMentions(text, guild) + .Select(x => guild?.GetChannel(x)) + .Where(x => x != null).ToImmutableArray(); + _mentionedRoles = MentionUtils.GetRoleMentions(text, guild); + _emojis = MessageHelper.GetEmojis(text); model.Content = text; } } From b6007630713f39244a058d03300be24a6461b277 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 05:43:01 -0300 Subject: [PATCH 051/102] Fixed null RestMessage.Author --- src/Discord.Net.Rest/Entities/Messages/RestMessage.cs | 6 ++++-- src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs | 6 +++--- src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 3ef933afb..3da05a05d 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -11,7 +11,7 @@ namespace Discord.Rest private long _timestampTicks; public ulong ChannelId { get; } - public IUser Author { get; } + public RestUser Author { get; } public string Content { get; private set; } @@ -28,10 +28,11 @@ namespace Discord.Rest public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal RestMessage(BaseDiscordClient discord, ulong id, ulong channelId) + internal RestMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) : base(discord, id) { ChannelId = channelId; + Author = author; } internal static RestMessage Create(BaseDiscordClient discord, Model model) { @@ -58,6 +59,7 @@ namespace Discord.Rest public override string ToString() => Content; MessageType IMessage.Type => MessageType.Default; + IUser IMessage.Author => Author; IReadOnlyCollection IMessage.Attachments => Attachments; IReadOnlyCollection IMessage.Embeds => Embeds; IReadOnlyCollection IMessage.MentionedRoles => MentionedRoles; diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index 7c62b4217..0142aa314 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -8,13 +8,13 @@ namespace Discord.Rest { public MessageType Type { get; private set; } - internal RestSystemMessage(BaseDiscordClient discord, ulong id, ulong channelId) - : base(discord, id, channelId) + internal RestSystemMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) + : base(discord, id, channelId, author) { } internal new static RestSystemMessage Create(BaseDiscordClient discord, Model model) { - var entity = new RestSystemMessage(discord, model.Id, model.ChannelId); + var entity = new RestSystemMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value)); entity.Update(model); return entity; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 88855f33c..e97db2a0e 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -33,13 +33,13 @@ namespace Discord.Rest public override IReadOnlyCollection MentionedRoles => _mentionedRoles; public override IReadOnlyCollection MentionedUsers => _mentionedUsers; - internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId) - : base(discord, id, channelId) + internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) + : base(discord, id, channelId, author) { } internal new static RestUserMessage Create(BaseDiscordClient discord, Model model) { - var entity = new RestUserMessage(discord, model.Id, model.ChannelId); + var entity = new RestUserMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value)); entity.Update(model); return entity; } From abd315ed21e24b7e67c8796e7904a96f93dc6cbf Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 05:46:37 -0300 Subject: [PATCH 052/102] Added length to Emoji --- src/Discord.Net.Core/Entities/Messages/Emoji.cs | 4 +++- src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/Emoji.cs b/src/Discord.Net.Core/Entities/Messages/Emoji.cs index 0fdbc69e2..5750b7ed8 100644 --- a/src/Discord.Net.Core/Entities/Messages/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Messages/Emoji.cs @@ -7,14 +7,16 @@ namespace Discord public ulong Id { get; } public string Name { get; } public int Index { get; } + public int Length { get; } public string Url => CDN.GetEmojiUrl(Id); - internal Emoji(ulong id, string name, int index) + internal Emoji(ulong id, string name, int index, int length) { Id = id; Name = name; Index = index; + Length = length; } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 085f73741..373c6bb52 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -45,7 +45,7 @@ namespace Discord.Rest { ulong id; if (ulong.TryParse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - builder.Add(new Emoji(id, match.Groups[1].Value, match.Index)); + builder.Add(new Emoji(id, match.Groups[1].Value, match.Index, match.Length)); } return builder.ToImmutable(); } From 090a1bc736397342d47d22d006366bf9dcd80fea Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 09:06:04 -0300 Subject: [PATCH 053/102] Added IMessage.Tags --- .../Extensions/MessageExtensions.cs | 2 +- .../Entities/Channels/IChannel.cs | 3 + .../Entities/Channels/IGuildChannel.cs | 2 - .../Entities/Messages/EmbedProvider.cs | 7 +- .../Entities/Messages/EmbedThumbnail.cs | 7 +- .../Entities/Messages/Emoji.cs | 42 ++- .../Entities/Messages/IMessage.cs | 4 +- .../Entities/Messages/ITag.cs | 11 + .../Entities/Messages/IUserMessage.cs | 19 +- .../Mentions/EveryoneMentionHandling.cs | 9 - .../Messages/Mentions/RoleMentionHandling.cs | 10 - .../Messages/Mentions/UserMentionHandling.cs | 11 - src/Discord.Net.Core/Entities/Messages/Tag.cs | 28 ++ ...annelMentionHandling.cs => TagHandling.cs} | 3 +- .../Entities/Messages/TagType.cs | 12 + src/Discord.Net.Core/Utils/MentionUtils.cs | 322 ++++++++---------- src/Discord.Net.Core/project.json | 3 +- .../Entities/Channels/ChannelHelper.cs | 26 +- .../Entities/Channels/RestChannel.cs | 4 +- .../Entities/Channels/RestDMChannel.cs | 20 +- .../Entities/Channels/RestGroupChannel.cs | 16 +- .../Entities/Channels/RestTextChannel.cs | 16 +- .../Entities/Messages/MessageHelper.cs | 94 ++++- .../Entities/Messages/RestMessage.cs | 12 +- .../Entities/Messages/RestSystemMessage.cs | 8 +- .../Entities/Messages/RestUserMessage.cs | 50 +-- .../Entities/Channels/SocketChannel.cs | 4 +- .../Entities/Channels/SocketChannelHelper.cs | 6 +- .../Entities/Channels/SocketDMChannel.cs | 24 +- .../Entities/Channels/SocketGroupChannel.cs | 22 +- .../Entities/Channels/SocketTextChannel.cs | 22 +- .../Entities/Messages/SocketMessage.cs | 2 +- .../Entities/Messages/SocketUserMessage.cs | 44 +-- 33 files changed, 461 insertions(+), 404 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/ITag.cs delete mode 100644 src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs delete mode 100644 src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs delete mode 100644 src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/Tag.cs rename src/Discord.Net.Core/Entities/Messages/{Mentions/ChannelMentionHandling.cs => TagHandling.cs} (69%) create mode 100644 src/Discord.Net.Core/Entities/Messages/TagType.cs diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index 05da07187..4354cbb88 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -32,7 +32,7 @@ if (text.Length < endPos + 2 || text[endPos + 1] != ' ') return false; //Must end in "> " ulong userId; - if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 2), out userId)) return false; + if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out userId)) return false; if (userId == user.Id) { argPos = endPos + 2; diff --git a/src/Discord.Net.Core/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs index 97e58355c..72608ec6a 100644 --- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -5,6 +5,9 @@ namespace Discord { public interface IChannel : ISnowflakeEntity { + /// 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); diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index bb9f39c71..81bf42d8e 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -7,8 +7,6 @@ namespace Discord { public interface IGuildChannel : IChannel, IDeletable { - /// Gets the name of this channel. - string Name { get; } /// Gets the position of this channel in the guild's channel list, relative to others of the same type. int Position { get; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 8cc650ce6..64b13e8e3 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.EmbedProvider; +using System.Diagnostics; +using Model = Discord.API.EmbedProvider; namespace Discord { + [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedProvider { public string Name { get; } @@ -16,5 +18,8 @@ namespace Discord { return new EmbedProvider(model.Name, model.Url); } + + private string DebuggerDisplay => $"{Name} ({Url})"; + public override string ToString() => Name; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 43a37548c..6a5fc4163 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.EmbedThumbnail; +using System.Diagnostics; +using Model = Discord.API.EmbedThumbnail; namespace Discord { + [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct EmbedThumbnail { public string Url { get; } @@ -22,5 +24,8 @@ namespace Discord model.Height.IsSpecified ? model.Height.Value : (int?)null, model.Width.IsSpecified ? model.Width.Value : (int?)null); } + + private string DebuggerDisplay => $"{ToString()} ({Url})"; + public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0"; } } diff --git a/src/Discord.Net.Core/Entities/Messages/Emoji.cs b/src/Discord.Net.Core/Entities/Messages/Emoji.cs index 5750b7ed8..612e99f29 100644 --- a/src/Discord.Net.Core/Entities/Messages/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Messages/Emoji.cs @@ -1,22 +1,54 @@ using Discord.API; +using System; +using System.Diagnostics; +using System.Globalization; namespace Discord { + [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct Emoji { public ulong Id { get; } public string Name { get; } - public int Index { get; } - public int Length { get; } public string Url => CDN.GetEmojiUrl(Id); - internal Emoji(ulong id, string name, int index, int length) + internal Emoji(ulong id, string name) { Id = id; Name = name; - Index = index; - Length = length; } + + public static Emoji Parse(string text) + { + Emoji result; + if (TryParse(text, out result)) + return result; + throw new ArgumentException("Invalid emoji format", nameof(text)); + } + + public static bool TryParse(string text, out Emoji result) + { + result = default(Emoji); + if (text.Length >= 4 && text[0] == '<' && text[1] == ':' && text[text.Length - 1] == '>') + { + int splitIndex = text.IndexOf(':', 2); + if (splitIndex == -1) + return false; + + ulong id; + if (!ulong.TryParse(text.Substring(splitIndex + 1, text.Length - splitIndex - 2), NumberStyles.None, CultureInfo.InvariantCulture, out id)) + return false; + + string name = text.Substring(2, splitIndex - 2); + result = new Emoji(id, name); + return true; + } + return false; + + } + + private string DebuggerDisplay => $"{Name} ({Id})"; + public override string ToString() => Name; } } diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 6291c1a3d..6f0a60e0f 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -27,8 +27,8 @@ namespace Discord IReadOnlyCollection Attachments { get; } /// Returns a collection of all embeds included in this message. IReadOnlyCollection Embeds { get; } - /// Returns a collection of all custom emoji included in this message. - IReadOnlyCollection Emojis { get; } + /// Returns a collection of all tags included in this message's content. + IReadOnlyCollection Tags { get; } /// Returns a collection of channel ids mentioned in this message. IReadOnlyCollection MentionedChannelIds { get; } /// Returns a collection of roles mentioned in this message. diff --git a/src/Discord.Net.Core/Entities/Messages/ITag.cs b/src/Discord.Net.Core/Entities/Messages/ITag.cs new file mode 100644 index 000000000..27824e6d3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/ITag.cs @@ -0,0 +1,11 @@ +namespace Discord +{ + public interface ITag + { + int Index { get; } + int Length { get; } + TagType Type { get; } + ulong Key { get; } + object Value { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 3faa31419..661a6d59a 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -12,18 +12,13 @@ namespace Discord Task PinAsync(RequestOptions options = null); /// Removes this message from its channel's pinned messages. Task UnpinAsync(RequestOptions options = null); - - /// Transforms this message's text into a human readable form, resolving mentions to that object's name. - string Resolve(int startIndex, int length, - UserMentionHandling userHandling = UserMentionHandling.Name, - ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, - EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore); - /// Transforms this message's text into a human readable form, resolving mentions to that object's name. + + /// Transforms this message's text into a human readable form by resolving its tags. string Resolve( - UserMentionHandling userHandling = UserMentionHandling.Name, - ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, - EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore); + TagHandling userHandling = TagHandling.Name, + TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, + TagHandling everyoneHandling = TagHandling.Ignore, + TagHandling emojiHandling = TagHandling.Name); } } diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs deleted file mode 100644 index 5e05606e5..000000000 --- a/src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Discord -{ - public enum EveryoneMentionHandling - { - Ignore = 0, - Remove, - Sanitize - } -} diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs deleted file mode 100644 index 94d4a382f..000000000 --- a/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Discord -{ - public enum RoleMentionHandling - { - Ignore = 0, - Remove, - Name, - Sanitize - } -} diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs deleted file mode 100644 index 42914f393..000000000 --- a/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Discord -{ - public enum UserMentionHandling - { - Ignore = 0, - Remove, - Name, - NameAndDiscriminator, - Sanitize - } -} diff --git a/src/Discord.Net.Core/Entities/Messages/Tag.cs b/src/Discord.Net.Core/Entities/Messages/Tag.cs new file mode 100644 index 000000000..06d995e73 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/Tag.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; + +namespace Discord +{ + [DebuggerDisplay(@"{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; + Index = index; + Length = length; + Key = key; + Value = value; + } + + private string DebuggerDisplay => $"{Value?.ToString() ?? "null"} ({Type})"; + public override string ToString() => $"{Value?.ToString() ?? "null"} ({Type})"; + + object ITag.Value => Value; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs similarity index 69% rename from src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs rename to src/Discord.Net.Core/Entities/Messages/TagHandling.cs index ea1b91688..3572a37a5 100644 --- a/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs +++ b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs @@ -1,10 +1,11 @@ namespace Discord { - public enum ChannelMentionHandling + public enum TagHandling { Ignore = 0, Remove, Name, + FullName, Sanitize } } diff --git a/src/Discord.Net.Core/Entities/Messages/TagType.cs b/src/Discord.Net.Core/Entities/Messages/TagType.cs new file mode 100644 index 000000000..2d93bb3e3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/TagType.cs @@ -0,0 +1,12 @@ +namespace Discord +{ + public enum TagType + { + UserMention, + ChannelMention, + RoleMention, + EveryoneMention, + HereMention, + Emoji + } +} diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 51463cdae..58e3650fe 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Globalization; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text; namespace Discord { @@ -11,10 +8,6 @@ namespace Discord { private const char SanitizeChar = '\x200b'; - private static readonly Regex _userRegex = new Regex(@"<@!?([0-9]+)>", RegexOptions.Compiled); - private static readonly Regex _channelRegex = new Regex(@"<#([0-9]+)>", RegexOptions.Compiled); - private static readonly Regex _roleRegex = new Regex(@"<@&([0-9]+)>", RegexOptions.Compiled); - //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); @@ -24,25 +17,24 @@ namespace Discord public static string MentionRole(ulong id) => MentionRole(id.ToString()); /// Parses a provided user mention string. - public static ulong ParseUser(string mentionText) + public static ulong ParseUser(string text) { ulong id; - if (TryParseUser(mentionText, out id)) + if (TryParseUser(text, out id)) return id; - throw new ArgumentException("Invalid mention format", nameof(mentionText)); + throw new ArgumentException("Invalid mention format", nameof(text)); } /// Tries to parse a provided user mention string. - public static bool TryParseUser(string mentionText, out ulong userId) + public static bool TryParseUser(string text, out ulong userId) { - mentionText = mentionText.Trim(); - if (mentionText.Length >= 3 && mentionText[0] == '<' && mentionText[1] == '@' && mentionText[mentionText.Length - 1] == '>') + if (text.Length >= 3 && text[0] == '<' && text[1] == '@' && text[text.Length - 1] == '>') { - if (mentionText.Length >= 4 && mentionText[2] == '!') - mentionText = mentionText.Substring(3, mentionText.Length - 4); //<@!123> + if (text.Length >= 4 && text[2] == '!') + text = text.Substring(3, text.Length - 4); //<@!123> else - mentionText = mentionText.Substring(2, mentionText.Length - 3); //<@123> + text = text.Substring(2, text.Length - 3); //<@123> - if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out userId)) + if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out userId)) return true; } userId = 0; @@ -50,22 +42,21 @@ namespace Discord } /// Parses a provided channel mention string. - public static ulong ParseChannel(string mentionText) + public static ulong ParseChannel(string text) { ulong id; - if (TryParseChannel(mentionText, out id)) + if (TryParseChannel(text, out id)) return id; - throw new ArgumentException("Invalid mention format", nameof(mentionText)); + throw new ArgumentException("Invalid mention format", nameof(text)); } /// Tries to parse a provided channel mention string. - public static bool TryParseChannel(string mentionText, out ulong channelId) + public static bool TryParseChannel(string text, out ulong channelId) { - mentionText = mentionText.Trim(); - if (mentionText.Length >= 3 && mentionText[0] == '<' && mentionText[1] == '#' && mentionText[mentionText.Length - 1] == '>') + if (text.Length >= 3 && text[0] == '<' && text[1] == '#' && text[text.Length - 1] == '>') { - mentionText = mentionText.Substring(2, mentionText.Length - 3); //<#123> + text = text.Substring(2, text.Length - 3); //<#123> - if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out channelId)) + if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out channelId)) return true; } channelId = 0; @@ -73,212 +64,175 @@ namespace Discord } /// Parses a provided role mention string. - public static ulong ParseRole(string mentionText) + public static ulong ParseRole(string text) { ulong id; - if (TryParseRole(mentionText, out id)) + if (TryParseRole(text, out id)) return id; - throw new ArgumentException("Invalid mention format", nameof(mentionText)); + throw new ArgumentException("Invalid mention format", nameof(text)); } /// Tries to parse a provided role mention string. - public static bool TryParseRole(string mentionText, out ulong roleId) + public static bool TryParseRole(string text, out ulong roleId) { - mentionText = mentionText.Trim(); - if (mentionText.Length >= 4 && mentionText[0] == '<' && mentionText[1] == '@' && mentionText[2] == '&' && mentionText[mentionText.Length - 1] == '>') + if (text.Length >= 4 && text[0] == '<' && text[1] == '@' && text[2] == '&' && text[text.Length - 1] == '>') { - mentionText = mentionText.Substring(3, mentionText.Length - 4); //<@&123> + text = text.Substring(3, text.Length - 4); //<@&123> - if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out roleId)) + if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out roleId)) return true; } roleId = 0; return false; } - internal static ImmutableArray GetUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentionedUsers) - where TUser : class, IUser + internal static string Resolve(IMessage msg, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling) { - var matches = _userRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); - foreach (var match in matches.OfType()) + var text = new StringBuilder(msg.Content); + var tags = msg.Tags; + int indexOffset = 0; + + foreach (var tag in tags) { - ulong id; - if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + string newText = ""; + switch (tag.Type) { - TUser user = null; - - //Verify this user was actually mentioned - foreach (var userMention in mentionedUsers) - { - if (userMention.Id == id) - { - user = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult() as TUser; - if (user == null) //User not found, fallback to basic mention info - user = userMention; - break; - } - } - - if (user != null) - builder.Add(user); + case TagType.UserMention: + if (userHandling == TagHandling.Ignore) continue; + newText = ResolveUserMention(tag, userHandling); + break; + case TagType.ChannelMention: + if (channelHandling == TagHandling.Ignore) continue; + newText = ResolveChannelMention(tag, channelHandling); + break; + case TagType.RoleMention: + if (roleHandling == TagHandling.Ignore) continue; + newText = ResolveRoleMention(tag, roleHandling); + break; + case TagType.EveryoneMention: + if (everyoneHandling == TagHandling.Ignore) continue; + newText = ResolveEveryoneMention(tag, everyoneHandling); + break; + case TagType.HereMention: + if (everyoneHandling == TagHandling.Ignore) continue; + newText = ResolveHereMention(tag, everyoneHandling); + break; + case TagType.Emoji: + if (emojiHandling == TagHandling.Ignore) continue; + newText = ResolveEmoji(tag, emojiHandling); + break; } + text.Remove(tag.Index, tag.Length); + text.Insert(tag.Index, newText); + indexOffset += newText.Length - tag.Length; } - return builder.ToImmutable(); + return text.ToString(); } - internal static ImmutableArray GetChannelMentions(string text, IGuild guild) + internal static string ResolveUserMention(ITag tag, TagHandling mode) { - var matches = _channelRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); - foreach (var match in matches.OfType()) + if (mode != TagHandling.Remove) { - ulong id; - if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - builder.Add(id); + var user = tag.Value as IUser; + switch (mode) + { + case TagHandling.Name: + if (user != null) + return $"@{(user as IGuildUser)?.Nickname ?? user?.Username}"; + else + return $"@unknown-user"; + case TagHandling.FullName: + if (user != null) + return $"@{(user as IGuildUser)?.Nickname ?? user?.Username}#{user.Discriminator}"; + else + return $"@unknown-user"; + case TagHandling.Sanitize: + return MentionUser($"{SanitizeChar}{tag.Key}"); + } } - return builder.ToImmutable(); + return ""; } - internal static ImmutableArray GetRoleMentions(string text, IGuild guild) - where TRole : class, IRole + internal static string ResolveChannelMention(ITag tag, TagHandling mode) { - if (guild == null) - return ImmutableArray.Create(); - - var matches = _roleRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); - foreach (var match in matches.OfType()) + if (mode != TagHandling.Remove) { - ulong id; - if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + var channel = tag.Value as IChannel; + switch (mode) { - var role = guild.GetRole(id) as TRole; - if (role != null) - builder.Add(role); + case TagHandling.Name: + case TagHandling.FullName: + if (channel != null) + return $"#{channel.Name}"; + else + return $"#deleted-channel"; + case TagHandling.Sanitize: + return MentionChannel($"{SanitizeChar}{tag.Key}"); } } - return builder.ToImmutable(); + return ""; } - - internal static string ResolveUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentions, UserMentionHandling mode) + internal static string ResolveRoleMention(ITag tag, TagHandling mode) { - if (mode == UserMentionHandling.Ignore) return text; - - return _userRegex.Replace(text, new MatchEvaluator(e => + if (mode != TagHandling.Remove) { - ulong id; - if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + var role = tag.Value as IRole; + switch (mode) { - IUser user = null; - foreach (var mention in mentions) - { - if (mention.Id == id) - { - user = mention; - break; - } - } - if (user != null) - { - string name = user.Username; - - var guildUser = user as IGuildUser; - if (e.Value[2] == '!') - { - if (guildUser != null && guildUser.Nickname != null) - name = guildUser.Nickname; - } - - switch (mode) - { - case UserMentionHandling.Name: - return $"@{name}"; - case UserMentionHandling.NameAndDiscriminator: - return $"@{name}#{user.Discriminator}"; - case UserMentionHandling.Sanitize: - return MentionUser($"{SanitizeChar}{id}"); - case UserMentionHandling.Remove: - default: - return ""; - } - } + case TagHandling.Name: + case TagHandling.FullName: + if (role != null) + return $"@{role.Name}"; + else + return $"@deleted-role"; + case TagHandling.Sanitize: + return MentionRole($"{SanitizeChar}{tag.Key}"); } - return e.Value; - })); + } + return ""; } - internal static string ResolveChannelMentions(string text, IGuild guild, ChannelMentionHandling mode) + internal static string ResolveEveryoneMention(ITag tag, TagHandling mode) { - if (mode == ChannelMentionHandling.Ignore) return text; - - return _channelRegex.Replace(text, new MatchEvaluator(e => + if (mode != TagHandling.Remove) { - ulong id; - if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + switch (mode) { - switch (mode) - { - case ChannelMentionHandling.Name: - IGuildChannel channel = null; - channel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); - if (channel != null) - return $"#{channel.Name}"; - else - return $"#deleted-channel"; - case ChannelMentionHandling.Sanitize: - return MentionChannel($"{SanitizeChar}{id}"); - case ChannelMentionHandling.Remove: - default: - return ""; - } + case TagHandling.Name: + case TagHandling.FullName: + return "@everyone"; + case TagHandling.Sanitize: + return $"@{SanitizeChar}everyone"; } - return e.Value; - })); + } + return ""; } - internal static string ResolveRoleMentions(string text, IReadOnlyCollection mentions, RoleMentionHandling mode) + internal static string ResolveHereMention(ITag tag, TagHandling mode) { - if (mode == RoleMentionHandling.Ignore) return text; - - return _roleRegex.Replace(text, new MatchEvaluator(e => + if (mode != TagHandling.Remove) { - ulong id; - if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + switch (mode) { - switch (mode) - { - case RoleMentionHandling.Name: - IRole role = null; - foreach (var mention in mentions) - { - if (mention.Id == id) - { - role = mention; - break; - } - } - if (role != null) - return $"{role.Name}"; - else - return $"deleted-role"; - case RoleMentionHandling.Sanitize: - return MentionRole($"{SanitizeChar}{id}"); - case RoleMentionHandling.Remove: - default: - return ""; - } + case TagHandling.Name: + case TagHandling.FullName: + return "@everyone"; + case TagHandling.Sanitize: + return $"@{SanitizeChar}everyone"; } - return e.Value; - })); + } + return ""; } - internal static string ResolveEveryoneMentions(string text, EveryoneMentionHandling mode) + internal static string ResolveEmoji(ITag tag, TagHandling mode) { - if (mode == EveryoneMentionHandling.Ignore) return text; - - switch (mode) + if (mode != TagHandling.Remove) { - case EveryoneMentionHandling.Sanitize: - return text.Replace("@everyone", $"@{SanitizeChar}everyone").Replace("@here", $"@{SanitizeChar}here"); - case EveryoneMentionHandling.Remove: - default: - return text.Replace("@everyone", "").Replace("@here", ""); + Emoji emoji = (Emoji)tag.Value; + switch (mode) + { + case TagHandling.Name: + case TagHandling.FullName: + return $":{emoji.Name}:"; + case TagHandling.Sanitize: + return $"<@{SanitizeChar}everyone"; + } } + return ""; } } } diff --git a/src/Discord.Net.Core/project.json b/src/Discord.Net.Core/project.json index 69d3fccb7..ea17308ae 100644 --- a/src/Discord.Net.Core/project.json +++ b/src/Discord.Net.Core/project.json @@ -35,8 +35,7 @@ "System.Net.WebSockets.Client": { "version": "4.0.0", "type": "build" - }, - "System.Text.RegularExpressions": "4.1.0" + } }, "frameworks": { diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index a6c172918..93e996747 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -63,13 +63,13 @@ namespace Discord.Rest //Messages public static async Task GetMessageAsync(IChannel channel, BaseDiscordClient client, - ulong id, RequestOptions options) + ulong id, IGuild guild, RequestOptions options) { var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); - return RestMessage.Create(client, model); + return RestMessage.Create(client, guild, model); } public static IAsyncEnumerable> GetMessagesAsync(IChannel channel, BaseDiscordClient client, - ulong? fromMessageId, Direction dir, int limit, RequestOptions options) + ulong? fromMessageId, Direction dir, int limit, IGuild guild, RequestOptions options) { //TODO: Test this with Around direction return new PagedAsyncEnumerable( @@ -84,7 +84,7 @@ namespace Discord.Rest if (info.Position != null) args.RelativeMessageId = info.Position.Value; var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options); - return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); ; + return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray(); ; }, nextPage: (info, lastPage) => { @@ -99,34 +99,34 @@ namespace Discord.Rest count: limit ); } - public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client, - RequestOptions options) + public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client, + IGuild guild, RequestOptions options) { var models = await client.ApiClient.GetPinsAsync(channel.Id, options).ConfigureAwait(false); - return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); + return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray(); } public static async Task SendMessageAsync(IChannel channel, BaseDiscordClient client, - string text, bool isTTS, RequestOptions options) + string text, bool isTTS, IGuild guild, RequestOptions options) { var args = new CreateMessageParams(text) { IsTTS = isTTS }; var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); - return RestUserMessage.Create(client, model); + return RestUserMessage.Create(client, guild, model); } public static Task SendFileAsync(IChannel channel, BaseDiscordClient client, - string filePath, string text, bool isTTS, RequestOptions options) + string filePath, string text, bool isTTS, IGuild guild, RequestOptions options) { string filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) - return SendFileAsync(channel, client, file, filename, text, isTTS, options); + return SendFileAsync(channel, client, file, filename, text, isTTS, guild, options); } public static async Task SendFileAsync(IChannel channel, BaseDiscordClient client, - Stream stream, string filename, string text, bool isTTS, RequestOptions options) + Stream stream, string filename, string text, bool isTTS, IGuild guild, RequestOptions options) { var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); - return RestUserMessage.Create(client, model); + return RestUserMessage.Create(client, guild, model); } public static async Task DeleteMessagesAsync(IChannel channel, BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 04305c6b8..342dd6898 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -43,7 +43,9 @@ namespace Discord.Rest public abstract Task UpdateAsync(RequestOptions options = null); - //IChannel + //IChannel + string IChannel.Name => null; + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overriden IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 9732c65e8..355bd656c 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -53,22 +53,22 @@ namespace Discord.Rest } public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); @@ -131,7 +131,9 @@ namespace Discord.Rest IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); - //IChannel + //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) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 5e9c0dcbf..9aeb99447 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -66,22 +66,22 @@ namespace Discord.Rest } public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index e8c82fcb5..2cb2a7d19 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -43,22 +43,22 @@ namespace Discord.Rest => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 373c6bb52..c62f59071 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -11,8 +10,6 @@ namespace Discord.Rest { internal static class MessageHelper { - private static readonly Regex _emojiRegex = new Regex(@"<:(.+?):(\d+?)>", RegexOptions.Compiled); - public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, RequestOptions options) { @@ -37,17 +34,94 @@ namespace Discord.Rest await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id, options); } - public static ImmutableArray GetEmojis(string text) + public static ImmutableArray ParseTags(string text, IMessageChannel channel, IGuild guild, ImmutableArray userMentions) { - var matches = _emojiRegex.Matches(text); - var builder = ImmutableArray.CreateBuilder(matches.Count); - foreach (var match in matches.OfType()) + var tags = new SortedList(); + + int index = 0; + while (true) { + index = text.IndexOf('<', index); + if (index == -1) break; + int endIndex = text.IndexOf('>', index + 1); + if (endIndex == -1) break; + string content = text.Substring(index, endIndex - index + 1); + ulong id; - if (ulong.TryParse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - builder.Add(new Emoji(id, match.Groups[1].Value, match.Index, match.Length)); + if (MentionUtils.TryParseUser(content, out id)) + { + IUser mentionedUser = null; + foreach (var mention in userMentions) + { + if (mention.Id == id) + { + mentionedUser = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); + if (mentionedUser == null) + mentionedUser = mention; + break; + } + } + tags.Add(index, new Tag(TagType.UserMention, index, content.Length, id, mentionedUser)); + } + else if (MentionUtils.TryParseChannel(content, out id)) + { + IChannel mentionedChannel = null; + if (guild != null) + mentionedChannel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); + tags.Add(index, new Tag(TagType.ChannelMention, index, content.Length, id, mentionedChannel)); + } + else if (MentionUtils.TryParseRole(content, out id)) + { + IRole mentionedRole = null; + if (guild != null) + mentionedRole = guild.GetRole(id); + tags.Add(index, new Tag(TagType.RoleMention, index, content.Length, id, mentionedRole)); + } + else + { + Emoji emoji; + if (Emoji.TryParse(content, out emoji)) + tags.Add(index, new Tag(TagType.Emoji, index, content.Length, id, emoji)); + } + index = endIndex + 1; + } + + index = 0; + while (true) + { + index = text.IndexOf("@everyone", index); + if (index == -1) break; + + tags.Add(index, new Tag(TagType.EveryoneMention, index, "@everyone".Length, 0, null)); + index++; } - return builder.ToImmutable(); + + index = 0; + while (true) + { + index = text.IndexOf("@here", index); + if (index == -1) break; + + tags.Add(index, new Tag(TagType.HereMention, index, "@here".Length, 0, null)); + index++; + } + + return tags.Values.ToImmutableArray(); + } + 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(); } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 3da05a05d..87999caa0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -8,6 +8,7 @@ namespace Discord.Rest { public abstract class RestMessage : RestEntity, IMessage, IUpdateable { + internal readonly IGuild _guild; private long _timestampTicks; public ulong ChannelId { get; } @@ -21,25 +22,26 @@ namespace Discord.Rest public virtual DateTimeOffset? EditedTimestamp => null; public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); - public virtual IReadOnlyCollection Emojis => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedChannelIds => 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 RestMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) + internal RestMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) : base(discord, id) { ChannelId = channelId; Author = author; + _guild = guild; } - internal static RestMessage Create(BaseDiscordClient discord, Model model) + internal static RestMessage Create(BaseDiscordClient discord, IGuild guild, Model model) { if (model.Type == MessageType.Default) - return RestUserMessage.Create(discord, model); + return RestUserMessage.Create(discord, guild, model); else - return RestSystemMessage.Create(discord, model); + return RestSystemMessage.Create(discord, guild, model); } internal virtual void Update(Model model) { diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index 0142aa314..58aac4129 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -8,13 +8,13 @@ namespace Discord.Rest { public MessageType Type { get; private set; } - internal RestSystemMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) - : base(discord, id, channelId, author) + internal RestSystemMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) + : base(discord, id, channelId, author, guild) { } - internal new static RestSystemMessage Create(BaseDiscordClient discord, Model model) + internal new static RestSystemMessage Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestSystemMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value)); + var entity = new RestSystemMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value), guild); entity.Update(model); return entity; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index e97db2a0e..682704b22 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Message; @@ -15,10 +16,7 @@ namespace Discord.Rest private long? _editedTimestampTicks; private ImmutableArray _attachments; private ImmutableArray _embeds; - private ImmutableArray _emojis; - private ImmutableArray _mentionedChannelIds; - private ImmutableArray _mentionedRoles; - private ImmutableArray _mentionedUsers; + private ImmutableArray _tags; public ulong? WebhookId { get; private set; } @@ -28,18 +26,18 @@ namespace Discord.Rest public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); public override IReadOnlyCollection Attachments => _attachments; public override IReadOnlyCollection Embeds => _embeds; - public override IReadOnlyCollection Emojis => _emojis; - public override IReadOnlyCollection MentionedChannelIds => _mentionedChannelIds; - public override IReadOnlyCollection MentionedRoles => _mentionedRoles; - public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + public override IReadOnlyCollection MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); + public override IReadOnlyCollection MentionedRoles => MessageHelper.FilterTagsByValue(TagType.RoleMention, _tags); + public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); + public override IReadOnlyCollection Tags => _tags; - internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) - : base(discord, id, channelId, author) + internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) + : base(discord, id, channelId, author, guild) { } - internal new static RestUserMessage Create(BaseDiscordClient discord, Model model) + internal new static RestUserMessage Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestUserMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value)); + var entity = new RestUserMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value), guild); entity.Update(model); return entity; } @@ -87,13 +85,13 @@ namespace Discord.Rest _embeds = ImmutableArray.Create(); } - ImmutableArray mentions = ImmutableArray.Create(); + ImmutableArray mentions = ImmutableArray.Create(); if (model.Mentions.IsSpecified) { var value = model.Mentions.Value; if (value.Length > 0) { - var newMentions = ImmutableArray.CreateBuilder(value.Length); + var newMentions = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) newMentions.Add(RestUser.Create(Discord, value[i])); mentions = newMentions.ToImmutable(); @@ -103,11 +101,7 @@ namespace Discord.Rest if (model.Content.IsSpecified) { var text = model.Content.Value; - - _mentionedUsers = MentionUtils.GetUserMentions(text, null, mentions); - _mentionedChannelIds = MentionUtils.GetChannelMentions(text, null); - _mentionedRoles = MentionUtils.GetRoleMentions(text, null); - _emojis = MessageHelper.GetEmojis(text); + _tags = MessageHelper.ParseTags(text, null, _guild, mentions); model.Content = text; } } @@ -122,21 +116,9 @@ namespace Discord.Rest public Task UnpinAsync(RequestOptions options) => MessageHelper.UnpinAsync(this, Discord, options); - public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) - => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(int startIndex, int length, UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) - => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, - RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) - { - text = MentionUtils.ResolveUserMentions(text, null, MentionedUsers, userHandling); - text = MentionUtils.ResolveChannelMentions(text, null, channelHandling); - text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling); - text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling); - return text; - } + 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, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 998e576f2..9601d5323 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -38,7 +38,9 @@ namespace Discord.WebSocket internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; - //IChannel + //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) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 5c02d457f..f91ab4c2d 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -9,7 +9,7 @@ namespace Discord.WebSocket internal static class SocketChannelHelper { public static IAsyncEnumerable> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, - ulong? fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) + ulong? fromMessageId, Direction dir, int limit, CacheMode mode, IGuild guild, RequestOptions options) { if (dir == Direction.Around) throw new NotImplementedException(); //TODO: Impl @@ -36,7 +36,7 @@ namespace Discord.WebSocket return result; //Download remaining messages - var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, cachedMessages.Min(x => x.Id), dir, limit, options); + var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, cachedMessages.Min(x => x.Id), dir, limit, guild, options); return result.Concat(downloadedMessages); } else @@ -45,7 +45,7 @@ namespace Discord.WebSocket return result; //Dont use cache in this case - return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options); + return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, guild, options); } } public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 00849537c..5e481d3f2 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -48,15 +48,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, null, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, null, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, null, 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) @@ -64,14 +64,14 @@ namespace Discord.WebSocket 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); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); @@ -123,11 +123,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, null, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, null, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, null, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) @@ -140,6 +140,8 @@ namespace Discord.WebSocket => 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) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index cc75139a1..00cf00082 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -71,15 +71,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, null, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, null, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, null, 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) @@ -87,14 +87,14 @@ namespace Discord.WebSocket 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); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); @@ -186,11 +186,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, null, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, null, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, null, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 4f84e6ee4..c35c6b370 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -54,15 +54,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, Guild, options); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, Guild, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, Guild, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, Guild, 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) @@ -70,14 +70,14 @@ namespace Discord.WebSocket 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); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, Guild, options); public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, Guild, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, Guild, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, Guild, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); @@ -124,11 +124,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, Guild, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, Guild, 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); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, Guild, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 9badd0e48..89bfbfb01 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -22,10 +22,10 @@ namespace Discord.WebSocket public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); - public virtual IReadOnlyCollection Emojis => 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); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 8a5b9e34c..249e7b3ae 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Message; @@ -17,10 +16,7 @@ namespace Discord.WebSocket private long? _editedTimestampTicks; private ImmutableArray _attachments; private ImmutableArray _embeds; - private ImmutableArray _emojis; - private ImmutableArray _mentionedChannels; - private ImmutableArray _mentionedRoles; - private ImmutableArray _mentionedUsers; + private ImmutableArray _tags; public ulong? WebhookId { get; private set; } @@ -31,10 +27,10 @@ namespace Discord.WebSocket public override IReadOnlyCollection Attachments => _attachments; public override IReadOnlyCollection Embeds => _embeds; - public override IReadOnlyCollection Emojis => _emojis; - public override IReadOnlyCollection MentionedChannels => _mentionedChannels; - public override IReadOnlyCollection MentionedRoles => _mentionedRoles; - public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + 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); internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) : base(discord, id, channel, author) @@ -90,13 +86,13 @@ namespace Discord.WebSocket _embeds = ImmutableArray.Create(); } - ImmutableArray mentions = ImmutableArray.Create(); + ImmutableArray mentions = ImmutableArray.Create(); if (model.Mentions.IsSpecified) { var value = model.Mentions.Value; if (value.Length > 0) { - var newMentions = ImmutableArray.CreateBuilder(value.Length); + var newMentions = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) newMentions.Add(SocketSimpleUser.Create(Discord, Discord.State, value[i])); mentions = newMentions.ToImmutable(); @@ -107,13 +103,7 @@ namespace Discord.WebSocket { var text = model.Content.Value; var guild = (Channel as SocketGuildChannel)?.Guild; - - _mentionedUsers = MentionUtils.GetUserMentions(text, Channel, mentions); - _mentionedChannels = MentionUtils.GetChannelMentions(text, guild) - .Select(x => guild?.GetChannel(x)) - .Where(x => x != null).ToImmutableArray(); - _mentionedRoles = MentionUtils.GetRoleMentions(text, guild); - _emojis = MessageHelper.GetEmojis(text); + _tags = MessageHelper.ParseTags(text, Channel, guild, mentions); model.Content = text; } } @@ -128,21 +118,9 @@ namespace Discord.WebSocket public Task UnpinAsync(RequestOptions options = null) => MessageHelper.UnpinAsync(this, Discord, options); - public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) - => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(int startIndex, int length, UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) - => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, - RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) - { - text = MentionUtils.ResolveUserMentions(text, null, MentionedUsers, userHandling); - text = MentionUtils.ResolveChannelMentions(text, null, channelHandling); - text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling); - text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling); - return text; - } + 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, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage; From dcde486aace4db8cf1e8c0febab0aa2e5402a623 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 09:20:25 -0300 Subject: [PATCH 054/102] Fixed crash when resolving a message with multiple tags --- src/Discord.Net.Core/Utils/MentionUtils.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 58e3650fe..006658643 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -121,8 +121,8 @@ namespace Discord newText = ResolveEmoji(tag, emojiHandling); break; } - text.Remove(tag.Index, tag.Length); - text.Insert(tag.Index, newText); + text.Remove(tag.Index + indexOffset, tag.Length); + text.Insert(tag.Index + indexOffset, newText); indexOffset += newText.Length - tag.Length; } return text.ToString(); @@ -132,20 +132,24 @@ namespace Discord if (mode != TagHandling.Remove) { var user = tag.Value as IUser; + var guildUser = user as IGuildUser; switch (mode) { case TagHandling.Name: if (user != null) - return $"@{(user as IGuildUser)?.Nickname ?? user?.Username}"; + return $"@{guildUser?.Nickname ?? user?.Username}"; else return $"@unknown-user"; case TagHandling.FullName: if (user != null) - return $"@{(user as IGuildUser)?.Nickname ?? user?.Username}#{user.Discriminator}"; + return $"@{guildUser?.Nickname ?? user?.Username}#{user.Discriminator}"; else return $"@unknown-user"; case TagHandling.Sanitize: - return MentionUser($"{SanitizeChar}{tag.Key}"); + if (guildUser != null && guildUser.Nickname == null) + return MentionUser($"{SanitizeChar}{tag.Key}", false); + else + return MentionUser($"{SanitizeChar}{tag.Key}", true); } } return ""; From edad75cef1931818ca72b42526a04098dc8fb87c Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 6 Oct 2016 12:47:54 -0300 Subject: [PATCH 055/102] Fixed resolving user mentions with FullName --- src/Discord.Net.Core/Utils/MentionUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 006658643..1b1408852 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -142,7 +142,7 @@ namespace Discord return $"@unknown-user"; case TagHandling.FullName: if (user != null) - return $"@{guildUser?.Nickname ?? user?.Username}#{user.Discriminator}"; + return $"@{user.Username}#{user.Discriminator}"; else return $"@unknown-user"; case TagHandling.Sanitize: From 81a0c12f4829436fb99e0c761317d38491c565bb Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 16:56:27 -0300 Subject: [PATCH 056/102] Moved RPC ClientId/Origin out of config --- src/Discord.Net.Rpc/DiscordRpcClient.cs | 11 ++++++----- src/Discord.Net.Rpc/DiscordRpcConfig.cs | 13 +------------ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 263d20b44..eedc98718 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -29,10 +29,11 @@ namespace Discord.Rpc public new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient; /// Creates a new RPC discord client. - public DiscordRpcClient(string clientId, string origin) : this(new DiscordRpcConfig(clientId, origin)) { } + public DiscordRpcClient(string clientId, string origin) + : this(clientId, origin, new DiscordRpcConfig()) { } /// Creates a new RPC discord client. - public DiscordRpcClient(DiscordRpcConfig config) - : base(config, CreateApiClient(config)) + public DiscordRpcClient(string clientId, string origin, DiscordRpcConfig config) + : base(config, CreateApiClient(clientId, origin, config)) { ConnectionTimeout = config.ConnectionTimeout; _rpcLogger = LogManager.CreateLogger("RPC"); @@ -57,8 +58,8 @@ namespace Discord.Rpc await _rpcLogger.WarningAsync($"Connection Closed").ConfigureAwait(false); }; } - private static API.DiscordRpcApiClient CreateApiClient(DiscordRpcConfig config) - => new API.DiscordRpcApiClient(config.ClientId, DiscordRestConfig.UserAgent, config.Origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); + private static API.DiscordRpcApiClient CreateApiClient(string clientId, string origin, DiscordRpcConfig config) + => new API.DiscordRpcApiClient(clientId, DiscordRestConfig.UserAgent, origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); /// public Task ConnectAsync() => ConnectAsync(false); diff --git a/src/Discord.Net.Rpc/DiscordRpcConfig.cs b/src/Discord.Net.Rpc/DiscordRpcConfig.cs index ac54551ed..8ca1ae32e 100644 --- a/src/Discord.Net.Rpc/DiscordRpcConfig.cs +++ b/src/Discord.Net.Rpc/DiscordRpcConfig.cs @@ -9,18 +9,7 @@ namespace Discord.Rpc public const int PortRangeStart = 6463; public const int PortRangeEnd = 6472; - - public DiscordRpcConfig(string clientId, string origin) - { - ClientId = clientId; - Origin = origin; - } - - /// Gets or sets the Discord client/application id used for this RPC connection. - public string ClientId { get; } - /// Gets or sets the origin used for this RPC connection. - public string Origin { get; } - + /// Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. public int ConnectionTimeout { get; set; } = 30000; From cabf5673598a76bf6896fd39ecdfefba9c6f2b8f Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 17:24:16 -0300 Subject: [PATCH 057/102] Renamed WebSocketMessage -> SocketFrame --- src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs | 6 +++--- src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs | 6 +++--- .../API/SocketFrame.cs} | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/{Discord.Net.Core/API/WebSocketMessage.cs => Discord.Net.WebSocket/API/SocketFrame.cs} (93%) diff --git a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs index 5aea2dcb4..b1bb61eb2 100644 --- a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs @@ -48,7 +48,7 @@ namespace Discord.API using (var reader = new StreamReader(decompressed)) using (var jsonReader = new JsonTextReader(reader)) { - var msg = _serializer.Deserialize(jsonReader); + var msg = _serializer.Deserialize(jsonReader); await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); } } @@ -58,7 +58,7 @@ namespace Discord.API using (var reader = new StringReader(text)) using (var jsonReader = new JsonTextReader(reader)) { - var msg = _serializer.Deserialize(jsonReader); + var msg = _serializer.Deserialize(jsonReader); await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); } }; @@ -164,7 +164,7 @@ namespace Discord.API //TODO: Add ETF byte[] bytes = null; - payload = new WebSocketMessage { Operation = (int)opCode, Payload = payload }; + payload = new SocketFrame { Operation = (int)opCode, Payload = payload }; if (payload != null) bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); await RequestQueue.SendAsync(new WebSocketRequest(_gatewayClient, bytes, true, options)).ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs index 62608b7e5..d7957b57b 100644 --- a/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs @@ -68,14 +68,14 @@ namespace Discord.Audio decompressed.Position = 0; using (var reader = new StreamReader(decompressed)) { - var msg = JsonConvert.DeserializeObject(reader.ReadToEnd()); + var msg = JsonConvert.DeserializeObject(reader.ReadToEnd()); await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); } } }; _webSocketClient.TextMessage += async text => { - var msg = JsonConvert.DeserializeObject(text); + var msg = JsonConvert.DeserializeObject(text); await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); }; _webSocketClient.Closed += async ex => @@ -103,7 +103,7 @@ namespace Discord.Audio public async Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) { byte[] bytes = null; - payload = new WebSocketMessage { Operation = (int)opCode, Payload = payload }; + payload = new SocketFrame { Operation = (int)opCode, Payload = payload }; if (payload != null) bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); await _webSocketClient.SendAsync(bytes, 0, bytes.Length, true).ConfigureAwait(false); diff --git a/src/Discord.Net.Core/API/WebSocketMessage.cs b/src/Discord.Net.WebSocket/API/SocketFrame.cs similarity index 93% rename from src/Discord.Net.Core/API/WebSocketMessage.cs rename to src/Discord.Net.WebSocket/API/SocketFrame.cs index e7c6a35b2..fd9367ca4 100644 --- a/src/Discord.Net.Core/API/WebSocketMessage.cs +++ b/src/Discord.Net.WebSocket/API/SocketFrame.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API { - public class WebSocketMessage + public class SocketFrame { [JsonProperty("op")] public int Operation { get; set; } From fc0b73f7a46db976583ca4cc528ffdd2df3fe80f Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 17:27:56 -0300 Subject: [PATCH 058/102] Renamed RpcMessage -> RpcFrame --- src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs | 8 ++++---- .../API/{Rpc/RpcMessage.cs => RpcFrame.cs} | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/Discord.Net.Rpc/API/{Rpc/RpcMessage.cs => RpcFrame.cs} (94%) diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index d413fd815..da6cce925 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -91,7 +91,7 @@ namespace Discord.API using (var reader = new StreamReader(decompressed)) using (var jsonReader = new JsonTextReader(reader)) { - var msg = _serializer.Deserialize(jsonReader); + var msg = _serializer.Deserialize(jsonReader); await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) ProcessMessage(msg); @@ -103,7 +103,7 @@ namespace Discord.API using (var reader = new StringReader(text)) using (var jsonReader = new JsonTextReader(reader)) { - var msg = _serializer.Deserialize(jsonReader); + var msg = _serializer.Deserialize(jsonReader); await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) ProcessMessage(msg); @@ -220,7 +220,7 @@ namespace Discord.API byte[] bytes = null; var guid = Guid.NewGuid(); - payload = new API.Rpc.RpcMessage { Cmd = cmd, Event = evt, Args = payload, Nonce = guid }; + payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = payload, Nonce = guid }; if (payload != null) { var json = SerializeJson(payload); @@ -351,7 +351,7 @@ namespace Discord.API return await SendRpcAsync("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); } - private bool ProcessMessage(API.Rpc.RpcMessage msg) + private bool ProcessMessage(API.Rpc.RpcFrame msg) { RpcRequest requestTracker; if (_requests.TryGetValue(msg.Nonce.Value.Value, out requestTracker)) diff --git a/src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs b/src/Discord.Net.Rpc/API/RpcFrame.cs similarity index 94% rename from src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs rename to src/Discord.Net.Rpc/API/RpcFrame.cs index 41616222c..cac150e3b 100644 --- a/src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs +++ b/src/Discord.Net.Rpc/API/RpcFrame.cs @@ -4,7 +4,7 @@ using System; namespace Discord.API.Rpc { - public class RpcMessage + public class RpcFrame { [JsonProperty("cmd")] public string Cmd { get; set; } From a74602d6e1786a2022f890663fe72da3a450eaee Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 17:37:12 -0300 Subject: [PATCH 059/102] Renamed Message model's Mentions -> UserMentions, added RoleMentions --- src/Discord.Net.Core/API/Common/Message.cs | 4 +++- src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs | 4 ++-- .../Entities/Messages/SocketUserMessage.cs | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Core/API/Common/Message.cs b/src/Discord.Net.Core/API/Common/Message.cs index fb89f9a60..58871fd73 100644 --- a/src/Discord.Net.Core/API/Common/Message.cs +++ b/src/Discord.Net.Core/API/Common/Message.cs @@ -27,7 +27,9 @@ namespace Discord.API [JsonProperty("mention_everyone")] public Optional MentionEveryone { get; set; } [JsonProperty("mentions")] - public Optional Mentions { get; set; } + public Optional UserMentions { get; set; } + [JsonProperty("mention_roles")] + public Optional RoleMentions { get; set; } [JsonProperty("attachments")] public Optional Attachments { get; set; } [JsonProperty("embeds")] diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 682704b22..f30f57023 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -86,9 +86,9 @@ namespace Discord.Rest } ImmutableArray mentions = ImmutableArray.Create(); - if (model.Mentions.IsSpecified) + if (model.UserMentions.IsSpecified) { - var value = model.Mentions.Value; + var value = model.UserMentions.Value; if (value.Length > 0) { var newMentions = ImmutableArray.CreateBuilder(value.Length); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 249e7b3ae..cf2d19e34 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -87,9 +87,9 @@ namespace Discord.WebSocket } ImmutableArray mentions = ImmutableArray.Create(); - if (model.Mentions.IsSpecified) + if (model.UserMentions.IsSpecified) { - var value = model.Mentions.Value; + var value = model.UserMentions.Value; if (value.Length > 0) { var newMentions = ImmutableArray.CreateBuilder(value.Length); From 95a8f36d635eea8bdfb96a576a381bed0947da14 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 18:19:26 -0300 Subject: [PATCH 060/102] Changed IMessage's MentionedUsers to MentionedUserIds --- .../Entities/Messages/IMessage.cs | 16 ++++++++-------- .../Entities/Messages/RestMessage.cs | 6 +++--- .../Entities/Messages/RestUserMessage.cs | 2 +- .../Entities/Messages/SocketMessage.cs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 6f0a60e0f..e5a5ebcbe 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -23,17 +23,17 @@ namespace Discord /// Gets the author of this message. IUser Author { get; } - /// Returns a collection of all attachments included in this message. + /// Returns all attachments included in this message. IReadOnlyCollection Attachments { get; } - /// Returns a collection of all embeds included in this message. + /// Returns all embeds included in this message. IReadOnlyCollection Embeds { get; } - /// Returns a collection of all tags included in this message's content. + /// Returns all tags included in this message's content. IReadOnlyCollection Tags { get; } - /// Returns a collection of channel ids mentioned in this message. + /// Returns the ids of channels mentioned in this message. IReadOnlyCollection MentionedChannelIds { get; } - /// Returns a collection of roles mentioned in this message. - IReadOnlyCollection MentionedRoles { get; } - /// Returns a collection of users mentioned in this message. - IReadOnlyCollection MentionedUsers { 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; } } } \ No newline at end of file diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 87999caa0..c2f9ab659 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Message; @@ -23,7 +24,7 @@ namespace Discord.Rest public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); @@ -64,7 +65,6 @@ namespace Discord.Rest IUser IMessage.Author => Author; IReadOnlyCollection IMessage.Attachments => Attachments; IReadOnlyCollection IMessage.Embeds => Embeds; - IReadOnlyCollection IMessage.MentionedRoles => MentionedRoles; - IReadOnlyCollection IMessage.MentionedUsers => MentionedUsers; + IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index f30f57023..1f27d762f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -27,7 +27,7 @@ namespace Discord.Rest public override IReadOnlyCollection Attachments => _attachments; public override IReadOnlyCollection Embeds => _embeds; public override IReadOnlyCollection MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); - public override IReadOnlyCollection MentionedRoles => MessageHelper.FilterTagsByValue(TagType.RoleMention, _tags); + public override IReadOnlyCollection MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); public override IReadOnlyCollection Tags => _tags; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 89bfbfb01..7093fabb6 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -60,8 +60,8 @@ namespace Discord.WebSocket IReadOnlyCollection IMessage.Attachments => Attachments; IReadOnlyCollection IMessage.Embeds => Embeds; IReadOnlyCollection IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); - IReadOnlyCollection IMessage.MentionedRoles => MentionedRoles; - IReadOnlyCollection IMessage.MentionedUsers => MentionedUsers; + IReadOnlyCollection IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); + IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); ulong IMessage.ChannelId => Channel.Id; } } From 007b4b11f0811b5993c27aaaf57557aec0c05f24 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 18:28:39 -0300 Subject: [PATCH 061/102] Added WebhookId/IsWebhook to IMessage --- src/Discord.Net.Core/Entities/Messages/IMessage.cs | 4 ++++ src/Discord.Net.Rest/Entities/Messages/RestMessage.cs | 3 ++- .../Entities/Messages/RestUserMessage.cs | 9 ++++----- .../Entities/Messages/SocketMessage.cs | 4 ++-- .../Entities/Messages/SocketUserMessage.cs | 8 +++----- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index e5a5ebcbe..6f00c9ba0 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -11,6 +11,8 @@ namespace Discord bool IsTTS { get; } /// Returns true if this message was added to its channel's pinned messages. bool IsPinned { get; } + /// Returns true if this message was created using a webhook. + bool IsWebhook { get; } /// Returns the content for this message. string Content { get; } /// Gets the time this message was sent. @@ -22,6 +24,8 @@ namespace Discord ulong ChannelId { get; } /// Gets the author of this message. IUser Author { get; } + /// Gets the id of the webhook used to created this message, if any. + ulong? WebhookId { get; } /// Returns all attachments included in this message. IReadOnlyCollection Attachments { get; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index c2f9ab659..41bb6c7f1 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -19,7 +19,6 @@ namespace Discord.Rest public virtual bool IsTTS => false; public virtual bool IsPinned => false; - public virtual bool IsWebhook => false; public virtual DateTimeOffset? EditedTimestamp => null; public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); @@ -27,6 +26,8 @@ namespace Discord.Rest public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); + public virtual ulong? WebhookId => null; + public bool IsWebhook => WebhookId != null; public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 1f27d762f..2d096434a 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -14,15 +14,14 @@ namespace Discord.Rest { private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; + private ulong? _webhookId; private ImmutableArray _attachments; private ImmutableArray _embeds; private ImmutableArray _tags; - - public ulong? WebhookId { get; private set; } - + public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; - public override bool IsWebhook => WebhookId != null; + public override ulong? WebhookId => _webhookId; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); public override IReadOnlyCollection Attachments => _attachments; public override IReadOnlyCollection Embeds => _embeds; @@ -55,7 +54,7 @@ namespace Discord.Rest if (model.MentionEveryone.IsSpecified) _isMentioningEveryone = model.MentionEveryone.Value; if (model.WebhookId.IsSpecified) - WebhookId = model.WebhookId.Value; + _webhookId = model.WebhookId.Value; if (model.Attachments.IsSpecified) { diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 7093fabb6..a3a4cb465 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -17,15 +17,15 @@ namespace Discord.WebSocket public virtual bool IsTTS => false; public virtual bool IsPinned => false; - public virtual bool IsWebhook => 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 virtual ulong? WebhookId => null; + public bool IsWebhook => WebhookId != null; public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index cf2d19e34..ecad183b0 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -14,17 +14,15 @@ namespace Discord.WebSocket { private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; + private ulong? _webhookId; private ImmutableArray _attachments; private ImmutableArray _embeds; private ImmutableArray _tags; - public ulong? WebhookId { get; private set; } - public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; - public override bool IsWebhook => WebhookId != null; + public override ulong? WebhookId => _webhookId; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); - public override IReadOnlyCollection Attachments => _attachments; public override IReadOnlyCollection Embeds => _embeds; public override IReadOnlyCollection Tags => _tags; @@ -56,7 +54,7 @@ namespace Discord.WebSocket if (model.MentionEveryone.IsSpecified) _isMentioningEveryone = model.MentionEveryone.Value; if (model.WebhookId.IsSpecified) - WebhookId = model.WebhookId.Value; + _webhookId = model.WebhookId.Value; if (model.Attachments.IsSpecified) { From c28751f6319d4397aef6506fb3d058b37b89c69d Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 19:07:22 -0300 Subject: [PATCH 062/102] Changed IMessage.ChannelId to IMessage.Channel --- .../Entities/Messages/IMessage.cs | 2 +- .../Channels/RestVirtualMessageChannel.cs | 106 ++++++++++++++++++ .../Entities/Messages/MessageHelper.cs | 8 +- .../Entities/Messages/RestMessage.cs | 8 +- .../Entities/Messages/RestSystemMessage.cs | 8 +- .../Entities/Messages/RestUserMessage.cs | 8 +- .../Entities/Messages/SocketMessage.cs | 2 +- 7 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 6f00c9ba0..9b15d1b07 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -21,7 +21,7 @@ namespace Discord DateTimeOffset? EditedTimestamp { get; } /// Gets the id of the channel this message was sent to. - ulong ChannelId { get; } + IMessageChannel Channel { get; } /// Gets the author of this message. IUser Author { get; } /// Gets the id of the webhook used to created this message, if any. diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs new file mode 100644 index 000000000..75f6e836f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs @@ -0,0 +1,106 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + //TODO: Review this class + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + internal class RestVirtualMessageChannel : RestEntity, IMessageChannel + { + 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, null, options); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + + public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, 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; + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(limit, options); + else + return AsyncEnumerable.Empty>(); + } + 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>(); + } + 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>(); + } + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); + + //IChannel + string IChannel.Name { get { throw new NotSupportedException(); } } + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + { + throw new NotSupportedException(); + } + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index c62f59071..a203b8e49 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -15,23 +15,23 @@ namespace Discord.Rest { var args = new ModifyMessageParams(); func(args); - await client.ApiClient.ModifyMessageAsync(msg.ChannelId, msg.Id, args, options); + await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, args, options); } public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.DeleteMessageAsync(msg.ChannelId, msg.Id, options); + await client.ApiClient.DeleteMessageAsync(msg.Channel.Id, msg.Id, options); } public static async Task PinAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.AddPinAsync(msg.ChannelId, msg.Id, options); + await client.ApiClient.AddPinAsync(msg.Channel.Id, msg.Id, options); } public static async Task UnpinAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id, options); + await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options); } public static ImmutableArray ParseTags(string text, IMessageChannel channel, IGuild guild, ImmutableArray userMentions) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 41bb6c7f1..ff54dad98 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -12,7 +12,7 @@ namespace Discord.Rest internal readonly IGuild _guild; private long _timestampTicks; - public ulong ChannelId { get; } + public IMessageChannel Channel { get; } public RestUser Author { get; } public string Content { get; private set; } @@ -31,10 +31,10 @@ namespace Discord.Rest public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal RestMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) + internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) : base(discord, id) { - ChannelId = channelId; + Channel = channel; Author = author; _guild = guild; } @@ -56,7 +56,7 @@ namespace Discord.Rest public async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetChannelMessageAsync(ChannelId, Id, options).ConfigureAwait(false); + var model = await Discord.ApiClient.GetChannelMessageAsync(Channel.Id, Id, options).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index 58aac4129..0725ab603 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -8,13 +8,15 @@ namespace Discord.Rest { public MessageType Type { get; private set; } - internal RestSystemMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) - : base(discord, id, channelId, author, guild) + internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) + : base(discord, id, channel, author, guild) { } internal new static RestSystemMessage Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestSystemMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value), guild); + var entity = new RestSystemMessage(discord, model.Id, + RestVirtualMessageChannel.Create(discord, model.ChannelId), + RestUser.Create(discord, model.Author.Value), guild); entity.Update(model); return entity; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 2d096434a..655929a61 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -30,13 +30,15 @@ namespace Discord.Rest public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); public override IReadOnlyCollection Tags => _tags; - internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) - : base(discord, id, channelId, author, guild) + internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) + : base(discord, id, channel, author, guild) { } internal new static RestUserMessage Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestUserMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value), guild); + var entity = new RestUserMessage(discord, model.Id, + RestVirtualMessageChannel.Create(discord, model.ChannelId), + RestUser.Create(discord, model.Author.Value), guild); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index a3a4cb465..709f36fa0 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -56,12 +56,12 @@ namespace Discord.WebSocket //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(); - ulong IMessage.ChannelId => Channel.Id; } } From e4a3a9e6e29adbeef245171b5d1ef810be2b6d25 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 21:18:43 -0300 Subject: [PATCH 063/102] Added ObjectOrId resolver --- src/Discord.Net.Core/API/Common/Message.cs | 2 +- src/Discord.Net.Core/API/ObjectOrId.cs | 19 +++++++++ .../Net/Converters/DiscordContractResolver.cs | 17 +++++++- .../Net/Converters/ObjectOrIdConverter.cs | 39 +++++++++++++++++++ .../Net/Converters/OptionalConverter.cs | 2 +- .../Entities/Messages/RestUserMessage.cs | 6 ++- .../Entities/Messages/SocketUserMessage.cs | 6 ++- 7 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 src/Discord.Net.Core/API/ObjectOrId.cs create mode 100644 src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs diff --git a/src/Discord.Net.Core/API/Common/Message.cs b/src/Discord.Net.Core/API/Common/Message.cs index 58871fd73..52de6f97b 100644 --- a/src/Discord.Net.Core/API/Common/Message.cs +++ b/src/Discord.Net.Core/API/Common/Message.cs @@ -27,7 +27,7 @@ namespace Discord.API [JsonProperty("mention_everyone")] public Optional MentionEveryone { get; set; } [JsonProperty("mentions")] - public Optional UserMentions { get; set; } + public Optional[]> UserMentions { get; set; } [JsonProperty("mention_roles")] public Optional RoleMentions { get; set; } [JsonProperty("attachments")] diff --git a/src/Discord.Net.Core/API/ObjectOrId.cs b/src/Discord.Net.Core/API/ObjectOrId.cs new file mode 100644 index 000000000..813aff906 --- /dev/null +++ b/src/Discord.Net.Core/API/ObjectOrId.cs @@ -0,0 +1,19 @@ +namespace Discord.API +{ + public struct ObjectOrId + { + public ulong Id { get; } + public T Object { get; } + + public ObjectOrId(ulong id) + { + Id = id; + Object = default(T); + } + public ObjectOrId(T obj) + { + Id = 0; + Object = obj; + } + } +} diff --git a/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs index 413fd512f..0fb676f1c 100644 --- a/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs @@ -24,8 +24,9 @@ namespace Discord.Net.Converters { JsonConverter converter; var type = propInfo.PropertyType; + Type genericType = type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null; - if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) + if (genericType == typeof(Optional<>)) { var typeInput = propInfo.DeclaringType; var innerTypeOutput = type.GenericTypeArguments[0]; @@ -46,6 +47,20 @@ namespace Discord.Net.Converters instanceField.SetValue(null, converter); } } + else if (genericType == typeof(ObjectOrId<>)) + { + var innerTypeOutput = type.GenericTypeArguments[0]; + + var converterType = typeof(ObjectOrIdConverter<>).MakeGenericType(innerTypeOutput).GetTypeInfo(); + var instanceField = converterType.GetDeclaredField("Instance"); + converter = instanceField.GetValue(null) as JsonConverter; + if (converter == null) + { + var innerConverter = GetConverter(propInfo, innerTypeOutput); + converter = converterType.DeclaredConstructors.First().Invoke(new object[] { innerConverter }) as JsonConverter; + instanceField.SetValue(null, converter); + } + } else converter = GetConverter(propInfo, type); diff --git a/src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs b/src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs new file mode 100644 index 000000000..a83edb60e --- /dev/null +++ b/src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs @@ -0,0 +1,39 @@ +using Discord.API; +using Newtonsoft.Json; +using System; + +namespace Discord.Net.Converters +{ + public class ObjectOrIdConverter : JsonConverter + { + internal static ObjectOrIdConverter Instance; + + private readonly JsonConverter _innerConverter; + + public override bool CanConvert(Type objectType) => true; + public override bool CanRead => true; + public override bool CanWrite => false; + + public ObjectOrIdConverter(JsonConverter innerConverter) + { + _innerConverter = innerConverter; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + switch (reader.TokenType) + { + case JsonToken.String: + case JsonToken.Integer: + return new ObjectOrId(ulong.Parse(reader.ReadAsString())); + default: + return new ObjectOrId(serializer.Deserialize(reader)); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new InvalidOperationException(); + } + } +} diff --git a/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs b/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs index 260d642d4..37bce7b28 100644 --- a/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs @@ -5,7 +5,7 @@ namespace Discord.Net.Converters { public class OptionalConverter : JsonConverter { - public static OptionalConverter Instance; + internal static OptionalConverter Instance; private readonly JsonConverter _innerConverter; diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 655929a61..4e73ad328 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -94,7 +94,11 @@ namespace Discord.Rest { var newMentions = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - newMentions.Add(RestUser.Create(Discord, value[i])); + { + var val = value[i]; + if (val.Object != null) + newMentions.Add(RestUser.Create(Discord, val.Object)); + } mentions = newMentions.ToImmutable(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index ecad183b0..91d798f5b 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -92,7 +92,11 @@ namespace Discord.WebSocket { var newMentions = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - newMentions.Add(SocketSimpleUser.Create(Discord, Discord.State, value[i])); + { + var val = value[i]; + if (val.Object != null) + newMentions.Add(SocketSimpleUser.Create(Discord, Discord.State, val.Object)); + } mentions = newMentions.ToImmutable(); } } From a1fd14fff0e9e496ff2622290efa42d16c666aa3 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 21:19:04 -0300 Subject: [PATCH 064/102] Updated Application model --- .../API/Common/Application.cs | 9 +++++---- .../Entities/RestApplication.cs | 7 +++++-- src/Discord.Net.Rpc/API/Rpc/Application.cs | 19 ------------------- 3 files changed, 10 insertions(+), 25 deletions(-) delete mode 100644 src/Discord.Net.Rpc/API/Rpc/Application.cs diff --git a/src/Discord.Net.Core/API/Common/Application.cs b/src/Discord.Net.Core/API/Common/Application.cs index f9fabe57d..e72c6ce79 100644 --- a/src/Discord.Net.Core/API/Common/Application.cs +++ b/src/Discord.Net.Core/API/Common/Application.cs @@ -11,13 +11,14 @@ namespace Discord.API public string[] RPCOrigins { get; set; } [JsonProperty("name")] public string Name { get; set; } - [JsonProperty("flags"), Int53] - public ulong Flags { get; set; } - [JsonProperty("owner")] - public User Owner { 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/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index 26456c79c..f06f03485 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -34,9 +34,12 @@ namespace Discord.Rest Description = model.Description; RPCOrigins = model.RPCOrigins; Name = model.Name; - Flags = model.Flags; - Owner = RestUser.Create(Discord, model.Owner); _iconId = model.Icon; + + if (model.Flags.IsSpecified) + Flags = model.Flags.Value; //TODO: Do we still need this? + if (model.Owner.IsSpecified) + Owner = RestUser.Create(Discord, model.Owner.Value); } public async Task UpdateAsync() diff --git a/src/Discord.Net.Rpc/API/Rpc/Application.cs b/src/Discord.Net.Rpc/API/Rpc/Application.cs deleted file mode 100644 index 6cc12a28b..000000000 --- a/src/Discord.Net.Rpc/API/Rpc/Application.cs +++ /dev/null @@ -1,19 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rpc -{ - public class Application - { - [JsonProperty("description")] - public string Description { get; set; } - [JsonProperty("icon")] - public string Icon { get; set; } - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("rpc_origins")] - public string[] RpcOrigins { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - } -} From 56209c24205d7044b3904e4c633e411a8c67101e Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 21:20:46 -0300 Subject: [PATCH 065/102] Added Rpc message entities --- src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs | 2 +- src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs | 17 +++ .../Entities/Messages/RpcMessage.cs | 65 +++++++++- .../Entities/Messages/RpcSystemMessage.cs | 33 +++++ .../Entities/Messages/RpcUserMessage.cs | 117 ++++++++++++++++++ src/Discord.Net.Rpc/Entities/Users/RpcUser.cs | 62 ++++++++++ 6 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs create mode 100644 src/Discord.Net.Rpc/Entities/Messages/RpcSystemMessage.cs create mode 100644 src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs create mode 100644 src/Discord.Net.Rpc/Entities/Users/RpcUser.cs diff --git a/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs b/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs index 41ff13288..75b35ef56 100644 --- a/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs @@ -7,6 +7,6 @@ namespace Discord.API.Rpc [JsonProperty("channel_id")] public ulong ChannelId { get; set; } [JsonProperty("message")] - public Message Message { get; set; } + public RpcMessage Message { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs b/src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs new file mode 100644 index 000000000..47c71bab0 --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class RpcMessage : Message + { + [JsonProperty("blocked")] + public Optional IsBlocked { get; } + [JsonProperty("content_parsed")] + public Optional ContentParsed { get; } + [JsonProperty("author_color")] + public Optional AuthorColor { get; } //#Hex + + [JsonProperty("mentions")] + public new Optional UserMentions { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs index f98067051..d9aa2b3f0 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs @@ -1,10 +1,65 @@ -namespace Discord.Rpc +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Model = Discord.API.Rpc.RpcMessage; + +namespace Discord.Rpc { - /*internal class RpcMessage : RpcEntity, IMessage + public abstract class RpcMessage : RpcEntity, IMessage { - internal RpcMessage(DiscordRpcClient discord, API.Message model) - : base(dicsord, model.Id) + private long _timestampTicks; + + public IMessageChannel Channel { get; } + public RpcUser Author { get; } + + public string Content { get; private set; } + public Color AuthorColor { get; private set; } + + public virtual bool IsTTS => false; + public virtual bool IsPinned => false; + public virtual bool IsBlocked => 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 MentionedUserIds => ImmutableArray.Create(); + public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); + public virtual ulong? WebhookId => null; + public bool IsWebhook => WebhookId != null; + + public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); + + internal RpcMessage(DiscordRpcClient discord, ulong id, IMessageChannel channel, RpcUser author) + : base(discord, id) { + Channel = channel; + Author = author; } - }*/ + internal static RpcMessage Create(DiscordRpcClient discord, ulong channelId, Model model) + { + //model.ChannelId is always 0, needs to be passed from the event + if (model.Type == MessageType.Default) + return RpcUserMessage.Create(discord, channelId, model); + else + return RpcSystemMessage.Create(discord, channelId, model); + } + internal virtual void Update(Model model) + { + if (model.Timestamp.IsSpecified) + _timestampTicks = model.Timestamp.Value.UtcTicks; + + if (model.Content.IsSpecified) + Content = model.Content.Value; + if (model.AuthorColor.IsSpecified) + AuthorColor = new Color(Convert.ToUInt32(model.AuthorColor.Value.Substring(1), 16)); + } + + public override string ToString() => Content; + + MessageType IMessage.Type => MessageType.Default; + IUser IMessage.Author => Author; + IReadOnlyCollection IMessage.Attachments => Attachments; + IReadOnlyCollection IMessage.Embeds => Embeds; + } } diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcSystemMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcSystemMessage.cs new file mode 100644 index 000000000..7cf222dc7 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcSystemMessage.cs @@ -0,0 +1,33 @@ +using Discord.Rest; +using System.Diagnostics; +using Model = Discord.API.Rpc.RpcMessage; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcSystemMessage : RpcMessage, ISystemMessage + { + public MessageType Type { get; private set; } + + internal RpcSystemMessage(DiscordRpcClient discord, ulong id, IMessageChannel channel, RpcUser author) + : base(discord, id, channel, author) + { + } + internal new static RpcSystemMessage Create(DiscordRpcClient discord, ulong channelId, Model model) + { + var entity = new RpcSystemMessage(discord, model.Id, + RestVirtualMessageChannel.Create(discord, channelId), + RpcUser.Create(discord, model.Author.Value)); + 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.Rpc/Entities/Messages/RpcUserMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs new file mode 100644 index 000000000..f400701f6 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs @@ -0,0 +1,117 @@ +using Discord.API.Rest; +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Rpc.RpcMessage; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcUserMessage : RpcMessage, IUserMessage + { + private bool _isMentioningEveryone, _isTTS, _isPinned, _isBlocked; + private long? _editedTimestampTicks; + private ulong? _webhookId; + private ImmutableArray _attachments; + private ImmutableArray _embeds; + private ImmutableArray _tags; + + public override bool IsTTS => _isTTS; + public override bool IsPinned => _isPinned; + public override bool IsBlocked => _isBlocked; + public override ulong? WebhookId => _webhookId; + 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 MentionedUserIds => MessageHelper.FilterTagsByKey(TagType.UserMention, _tags); + public override IReadOnlyCollection Tags => _tags; + + internal RpcUserMessage(DiscordRpcClient discord, ulong id, IMessageChannel channel, RpcUser author) + : base(discord, id, channel, author) + { + } + internal new static RpcUserMessage Create(DiscordRpcClient discord, ulong channelId, Model model) + { + var entity = new RpcUserMessage(discord, model.Id, + RestVirtualMessageChannel.Create(discord, channelId), + RpcUser.Create(discord, model.Author.Value)); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + base.Update(model); + + if (model.IsTextToSpeech.IsSpecified) + _isTTS = model.IsTextToSpeech.Value; + if (model.Pinned.IsSpecified) + _isPinned = model.Pinned.Value; + if (model.EditedTimestamp.IsSpecified) + _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; + if (model.MentionEveryone.IsSpecified) + _isMentioningEveryone = model.MentionEveryone.Value; + if (model.WebhookId.IsSpecified) + _webhookId = model.WebhookId.Value; + + if (model.IsBlocked.IsSpecified) + _isBlocked = model.IsBlocked.Value; + + if (model.Attachments.IsSpecified) + { + var value = model.Attachments.Value; + if (value.Length > 0) + { + var attachments = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + attachments.Add(Attachment.Create(value[i])); + _attachments = attachments.ToImmutable(); + } + else + _attachments = ImmutableArray.Create(); + } + + if (model.Embeds.IsSpecified) + { + var value = model.Embeds.Value; + if (value.Length > 0) + { + var embeds = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + embeds.Add(Embed.Create(value[i])); + _embeds = embeds.ToImmutable(); + } + else + _embeds = ImmutableArray.Create(); + } + + if (model.Content.IsSpecified) + { + var text = model.Content.Value; + _tags = MessageHelper.ParseTags(text, null, null, ImmutableArray.Create()); + model.Content = text; + } + } + + public Task ModifyAsync(Action func, RequestOptions options) + => MessageHelper.ModifyAsync(this, Discord, func, options); + public Task DeleteAsync(RequestOptions options) + => MessageHelper.DeleteAsync(this, Discord, options); + + public Task PinAsync(RequestOptions options) + => MessageHelper.PinAsync(this, Discord, options); + public Task UnpinAsync(RequestOptions options) + => 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, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); + + private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; + } +} diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs new file mode 100644 index 000000000..33db9bba1 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs @@ -0,0 +1,62 @@ +using Discord.Rest; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.User; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcUser : RpcEntity, IUser, IUpdateable + { + public bool IsBot { get; private set; } + public string Username { get; private set; } + public ushort DiscriminatorValue { get; private set; } + public string AvatarId { get; private set; } + + public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); + public string Discriminator => DiscriminatorValue.ToString("D4"); + public string Mention => MentionUtils.MentionUser(Id); + public virtual Game? Game => null; + public virtual UserStatus Status => UserStatus.Unknown; + + internal RpcUser(DiscordRpcClient discord, ulong id) + : base(discord, id) + { + } + internal static RpcUser Create(DiscordRpcClient discord, Model model) + { + var entity = new RpcUser(discord, model.Id); + entity.Update(model); + return entity; + } + internal virtual void Update(Model model) + { + if (model.Avatar.IsSpecified) + AvatarId = model.Avatar.Value; + if (model.Discriminator.IsSpecified) + DiscriminatorValue = ushort.Parse(model.Discriminator.Value); + if (model.Bot.IsSpecified) + IsBot = model.Bot.Value; + if (model.Username.IsSpecified) + Username = model.Username.Value; + } + + public virtual async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetUserAsync(Id, options); + Update(model); + } + + public Task CreateDMChannelAsync(RequestOptions options = null) + => UserHelper.CreateDMChannelAsync(this, Discord, options); + + public override string ToString() => $"{Username}#{Discriminator}"; + internal string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; + + //IUser + Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(null); + async Task IUser.CreateDMChannelAsync(RequestOptions options) + => await CreateDMChannelAsync(options); + } +} From b6823a4b45badb1c5df9a579f761b88b16d6c4b2 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 21:21:09 -0300 Subject: [PATCH 066/102] Updated xproj --- src/Discord.Net.Rpc/Discord.Net.Rpc.xproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj b/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj index c6ea6658f..c5d036842 100644 --- a/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj +++ b/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj @@ -4,18 +4,16 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - 5688a353-121e-40a1-8bfa-b17b91fb48fb - Discord.Net.Rpc + Discord.Rpc .\obj .\bin\ v4.6.1 - 2.0 - + \ No newline at end of file From 471089822b9f29971c3ceaa9a2f60c78b77b0225 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 21:39:48 -0300 Subject: [PATCH 067/102] Added MessageCreated/Updated events and several properties to RpcClient --- .../API/DiscordRestApiClient.cs | 5 ++- .../API/DiscordRpcApiClient.cs | 11 ++++-- .../API/Rpc/AuthorizeParams.cs | 3 +- .../DiscordRpcClient.Events.cs | 8 ++-- src/Discord.Net.Rpc/DiscordRpcClient.cs | 37 ++++++++++++------- src/Discord.Net.Rpc/DiscordRpcConfig.cs | 2 +- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index d8f279132..fc62d3be0 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -38,6 +38,7 @@ namespace Discord.API public TokenType AuthTokenType { get; private set; } public User CurrentUser { get; private set; } public RequestQueue RequestQueue { get; private set; } + internal bool FetchCurrentUser { get; set; } public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, JsonSerializer serializer = null, RequestQueue requestQueue = null) { @@ -45,6 +46,7 @@ namespace Discord.API _userAgent = userAgent; _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; RequestQueue = requestQueue; + FetchCurrentUser = true; _stateLock = new SemaphoreSlim(1, 1); @@ -113,7 +115,8 @@ namespace Discord.API _authToken = token; _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); - CurrentUser = await GetMyUserAsync(new RequestOptions { IgnoreState = true }); + if (FetchCurrentUser) + CurrentUser = await GetMyUserAsync(new RequestOptions { IgnoreState = true }); LoginState = LoginState.LoggedIn; } diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index da6cce925..e8789976e 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; @@ -67,13 +68,16 @@ namespace Discord.API public ConnectionState ConnectionState { get; private set; } - public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null) + public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, + JsonSerializer serializer = null, RequestQueue requestQueue = null) : base(restClientProvider, userAgent, serializer, requestQueue) { _connectionLock = new SemaphoreSlim(1, 1); _clientId = clientId; _origin = origin; + FetchCurrentUser = false; + _requestQueue = requestQueue ?? new RequestQueue(); _requests = new ConcurrentDictionary(); @@ -169,7 +173,7 @@ namespace Discord.API if (!success) throw new Exception("Unable to connect to the RPC server."); - + SetBaseUrl($"https://{uuid}.discordapp.io:{port}/"); ConnectionState = ConnectionState.Connected; } @@ -209,7 +213,6 @@ namespace Discord.API public async Task SendRpcAsync(string cmd, object payload, Optional evt = default(Optional), RequestOptions options = null) where TResponse : class { - options.IgnoreState = false; return await SendRpcAsyncInternal(cmd, payload, evt, options).ConfigureAwait(false); } private async Task SendRpcAsyncInternal(string cmd, object payload, Optional evt, RequestOptions options) @@ -246,7 +249,7 @@ namespace Discord.API options.IgnoreState = true; return await SendRpcAsync("AUTHENTICATE", msg, options: options).ConfigureAwait(false); } - public async Task SendAuthorizeAsync(string[] scopes, string rpcToken = null, RequestOptions options = null) + public async Task SendAuthorizeAsync(IReadOnlyCollection scopes, string rpcToken = null, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var msg = new AuthorizeParams diff --git a/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs b/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs index 606e96f9a..367aafd41 100644 --- a/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 using Newtonsoft.Json; +using System.Collections.Generic; namespace Discord.API.Rpc { @@ -8,7 +9,7 @@ namespace Discord.API.Rpc [JsonProperty("client_id")] public string ClientId { get; set; } [JsonProperty("scopes")] - public string[] Scopes { get; set; } + public IReadOnlyCollection Scopes { get; set; } [JsonProperty("rpc_token")] public Optional RpcToken { get; set; } } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs index 25989d01a..a746c180c 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs @@ -42,18 +42,18 @@ namespace Discord.Rpc private readonly AsyncEvent> _voiceStateUpdatedEvent = new AsyncEvent>(); //Messages - public event Func MessageReceived + public event Func MessageReceived { add { _messageReceivedEvent.Add(value); } remove { _messageReceivedEvent.Remove(value); } } - private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); - public event Func MessageUpdated + private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); + public event Func MessageUpdated { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _messageUpdatedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _messageUpdatedEvent = new AsyncEvent>(); public event Func MessageDeleted { add { _messageDeletedEvent.Add(value); } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index eedc98718..efbfb7126 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -6,6 +6,8 @@ using Discord.Rest; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -22,11 +24,15 @@ namespace Discord.Rpc private bool _canReconnect; public ConnectionState ConnectionState { get; private set; } + public IReadOnlyCollection Scopes { get; private set; } + public DateTimeOffset TokenExpiresAt { get; private set; } //From DiscordRpcConfig internal int ConnectionTimeout { get; private set; } public new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient; + public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } } + public RestApplication CurrentApplication { get; private set; } /// Creates a new RPC discord client. public DiscordRpcClient(string clientId, string origin) @@ -58,6 +64,7 @@ namespace Discord.Rpc await _rpcLogger.WarningAsync($"Connection Closed").ConfigureAwait(false); }; } + private static API.DiscordRpcApiClient CreateApiClient(string clientId, string origin, DiscordRpcConfig config) => new API.DiscordRpcApiClient(clientId, DiscordRestConfig.UserAgent, origin, config.RestClientProvider, config.WebSocketProvider, requestQueue: new RequestQueue()); @@ -286,19 +293,21 @@ namespace Discord.Rpc { await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); var data = (payload.Value as JToken).ToObject(_serializer); - var cancelToken = _cancelToken; + + RequestOptions options = new RequestOptions + { + //CancellationToken = _cancelToken //TODO: Implement + }; var _ = Task.Run(async () => { try { - RequestOptions options = new RequestOptions - { - //CancellationToken = cancelToken //TODO: Implement - }; - - if (LoginState != LoginState.LoggedOut) - await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); //Has bearer + var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); + CurrentUser = RestSelfUser.Create(this, response.User); + CurrentApplication = RestApplication.Create(this, response.Application); + Scopes = response.Scopes; + TokenExpiresAt = response.Expires; var __ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false); @@ -361,20 +370,20 @@ namespace Discord.Rpc //Messages case "MESSAGE_CREATE": { - /*await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); + await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); var data = (payload.Value as JToken).ToObject(_serializer); - var msg = new RpcMessage(this, data.Message); + var msg = RpcMessage.Create(this, data.ChannelId, data.Message); - await _messageReceivedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/ + await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); } break; case "MESSAGE_UPDATE": { - /*await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); + await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); var data = (payload.Value as JToken).ToObject(_serializer); - var msg = new RpcMessage(this, data.Message); + var msg = RpcMessage.Create(this, data.ChannelId, data.Message); - await _messageUpdatedEvent.InvokeAsync(data.ChannelId, msg).ConfigureAwait(false);*/ + await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false); } break; case "MESSAGE_DELETE": diff --git a/src/Discord.Net.Rpc/DiscordRpcConfig.cs b/src/Discord.Net.Rpc/DiscordRpcConfig.cs index 8ca1ae32e..d1e69376c 100644 --- a/src/Discord.Net.Rpc/DiscordRpcConfig.cs +++ b/src/Discord.Net.Rpc/DiscordRpcConfig.cs @@ -9,7 +9,7 @@ namespace Discord.Rpc public const int PortRangeStart = 6463; public const int PortRangeEnd = 6472; - + /// Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. public int ConnectionTimeout { get; set; } = 30000; From d36675cb32d722a4e3bfb6bfe1ef9cce23d2ba0d Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 22:25:12 -0300 Subject: [PATCH 068/102] Added RPC SpeakingStarted/Stopped events --- src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs | 9 +++++++++ src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs | 4 ++++ src/Discord.Net.Rpc/DiscordRpcClient.Events.cs | 14 ++++++++++---- src/Discord.Net.Rpc/DiscordRpcClient.cs | 11 +++++++---- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index e8789976e..1d0fab516 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -335,6 +335,15 @@ namespace Discord.API return await SendRpcAsync("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); } + public async Task SendGlobalSubscribeAsync(string evt, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + var msg = new ChannelSubscriptionParams + { + }; + return await SendRpcAsync("SUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); + } + public async Task SendGuildSubscribeAsync(string evt, ulong guildId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); diff --git a/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs b/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs index 828556818..4d8804d2f 100644 --- a/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs @@ -1,7 +1,11 @@ #pragma warning disable CS1591 +using Newtonsoft.Json; + namespace Discord.API.Rpc { public class SpeakingEvent { + [JsonProperty("user_id")] + public ulong UserId { get; set; } } } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs index a746c180c..3688bdbda 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs @@ -34,12 +34,18 @@ namespace Discord.Rpc private readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); //Voice - public event Func VoiceStateUpdated + public event Func SpeakingStarted { - add { _voiceStateUpdatedEvent.Add(value); } - remove { _voiceStateUpdatedEvent.Remove(value); } + add { _speakingStarted.Add(value); } + remove { _speakingStarted.Remove(value); } } - private readonly AsyncEvent> _voiceStateUpdatedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _speakingStarted = new AsyncEvent>(); + public event Func SpeakingStopped + { + add { _speakingStopped.Add(value); } + remove { _speakingStopped.Remove(value); } + } + private readonly AsyncEvent> _speakingStopped = new AsyncEvent>(); //Messages public event Func MessageReceived diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index efbfb7126..331b7e786 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -331,7 +331,7 @@ namespace Discord.Rpc break; //Voice - case "VOICE_STATE_CREATE": + /*case "VOICE_STATE_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false); @@ -351,19 +351,22 @@ namespace Discord.Rpc await _voiceStateUpdatedEvent.InvokeAsync().ConfigureAwait(false); } - break; + break;*/ case "SPEAKING_START": { await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false); - await _voiceStateUpdatedEvent.InvokeAsync().ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); + + await _speakingStarted.InvokeAsync(data.UserId).ConfigureAwait(false); } break; case "SPEAKING_STOP": { await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); - await _voiceStateUpdatedEvent.InvokeAsync().ConfigureAwait(false); + await _speakingStopped.InvokeAsync(data.UserId).ConfigureAwait(false); } break; From c6107103878e6b8b002f99fd62b218b52d2f8cbe Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 22:54:47 -0300 Subject: [PATCH 069/102] Added VoiceState RPC events --- src/Discord.Net.Rpc/API/Rpc/Pan.cs | 12 +++ .../API/Rpc/VoiceStateEvent.cs | 14 ++++ .../DiscordRpcClient.Events.cs | 33 ++++++-- src/Discord.Net.Rpc/DiscordRpcClient.cs | 20 +++-- src/Discord.Net.Rpc/Entities/Users/Pan.cs | 20 +++++ .../Entities/Users/RpcVoiceState.cs | 79 +++++++++++++++++++ 6 files changed, 165 insertions(+), 13 deletions(-) create mode 100644 src/Discord.Net.Rpc/API/Rpc/Pan.cs create mode 100644 src/Discord.Net.Rpc/Entities/Users/Pan.cs create mode 100644 src/Discord.Net.Rpc/Entities/Users/RpcVoiceState.cs diff --git a/src/Discord.Net.Rpc/API/Rpc/Pan.cs b/src/Discord.Net.Rpc/API/Rpc/Pan.cs new file mode 100644 index 000000000..e2a97c369 --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/Pan.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class Pan + { + [JsonProperty("left")] + public float Left { get; set; } + [JsonProperty("right")] + public float Right { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs index 445a02b80..11359fb8f 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs @@ -1,7 +1,21 @@ #pragma warning disable CS1591 +using Newtonsoft.Json; + namespace Discord.API.Rpc { public class VoiceStateEvent { + [JsonProperty("user")] + public User User { get; set; } + [JsonProperty("voice_state")] + public Optional VoiceState { get; set; } + [JsonProperty("nick")] + public Optional Nickname { get; set; } + [JsonProperty("volume")] + public Optional Volume { get; set; } + [JsonProperty("mute")] + public Optional Mute { get; set; } + [JsonProperty("pan")] + public Optional Pan { get; set; } } } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs index 3688bdbda..5639bdbae 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs @@ -34,18 +34,39 @@ namespace Discord.Rpc private readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); //Voice + public event Func VoiceStateCreated + { + add { _voiceStateCreatedEvent.Add(value); } + remove { _voiceStateCreatedEvent.Remove(value); } + } + private readonly AsyncEvent> _voiceStateCreatedEvent = new AsyncEvent>(); + + public event Func VoiceStateUpdated + { + add { _voiceStateUpdatedEvent.Add(value); } + remove { _voiceStateUpdatedEvent.Remove(value); } + } + private readonly AsyncEvent> _voiceStateUpdatedEvent = new AsyncEvent>(); + + public event Func VoiceStateDeleted + { + add { _voiceStateDeletedEvent.Add(value); } + remove { _voiceStateDeletedEvent.Remove(value); } + } + private readonly AsyncEvent> _voiceStateDeletedEvent = new AsyncEvent>(); + public event Func SpeakingStarted { - add { _speakingStarted.Add(value); } - remove { _speakingStarted.Remove(value); } + add { _speakingStartedEvent.Add(value); } + remove { _speakingStartedEvent.Remove(value); } } - private readonly AsyncEvent> _speakingStarted = new AsyncEvent>(); + private readonly AsyncEvent> _speakingStartedEvent = new AsyncEvent>(); public event Func SpeakingStopped { - add { _speakingStopped.Add(value); } - remove { _speakingStopped.Remove(value); } + add { _speakingStoppedEvent.Add(value); } + remove { _speakingStoppedEvent.Remove(value); } } - private readonly AsyncEvent> _speakingStopped = new AsyncEvent>(); + private readonly AsyncEvent> _speakingStoppedEvent = new AsyncEvent>(); //Messages public event Func MessageReceived diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 331b7e786..a5fcd827a 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -331,34 +331,40 @@ namespace Discord.Rpc break; //Voice - /*case "VOICE_STATE_CREATE": + case "VOICE_STATE_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); + var voiceState = RpcVoiceState.Create(this, data); - await _voiceStateUpdatedEvent.InvokeAsync().ConfigureAwait(false); + await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); } break; case "VOICE_STATE_UPDATE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); + var voiceState = RpcVoiceState.Create(this, data); - await _voiceStateUpdatedEvent.InvokeAsync().ConfigureAwait(false); + await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); } break; case "VOICE_STATE_DELETE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); + var voiceState = RpcVoiceState.Create(this, data); - await _voiceStateUpdatedEvent.InvokeAsync().ConfigureAwait(false); + await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false); } - break;*/ + break; case "SPEAKING_START": { await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false); var data = (payload.Value as JToken).ToObject(_serializer); - await _speakingStarted.InvokeAsync(data.UserId).ConfigureAwait(false); + await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); } break; case "SPEAKING_STOP": @@ -366,7 +372,7 @@ namespace Discord.Rpc await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false); var data = (payload.Value as JToken).ToObject(_serializer); - await _speakingStopped.InvokeAsync(data.UserId).ConfigureAwait(false); + await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); } break; diff --git a/src/Discord.Net.Rpc/Entities/Users/Pan.cs b/src/Discord.Net.Rpc/Entities/Users/Pan.cs new file mode 100644 index 000000000..8a64bddf3 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Users/Pan.cs @@ -0,0 +1,20 @@ +using Model = Discord.API.Rpc.Pan; + +namespace Discord.Rpc +{ + public struct Pan + { + public float Left { get; } + public float Right { get; } + + public Pan(float left, float right) + { + Left = left; + Right = right; + } + internal static Pan Create(Model model) + { + return new Pan(model.Left, model.Right); + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcVoiceState.cs b/src/Discord.Net.Rpc/Entities/Users/RpcVoiceState.cs new file mode 100644 index 000000000..ee392f5cd --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Users/RpcVoiceState.cs @@ -0,0 +1,79 @@ +using System; +using System.Diagnostics; +using Model = Discord.API.Rpc.VoiceStateEvent; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcVoiceState : IVoiceState + { + [Flags] + private enum Flags : byte + { + Normal = 0x00, + Suppressed = 0x01, + Muted = 0x02, + Deafened = 0x04, + SelfMuted = 0x08, + SelfDeafened = 0x10, + } + + private Flags _voiceStates; + + public RpcUser User { get; } + public string Nickname { get; private set; } + public int Volume { get; private set; } + public bool IsMuted2 { get; private set; } + public Pan Pan { get; private set; } + + public bool IsMuted => (_voiceStates & Flags.Muted) != 0; + public bool IsDeafened => (_voiceStates & Flags.Deafened) != 0; + public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0; + public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; + public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; + + internal RpcVoiceState(DiscordRpcClient discord, ulong userId) + { + User = new RpcUser(discord, userId); + } + internal static RpcVoiceState Create(DiscordRpcClient discord, Model model) + { + var entity = new RpcVoiceState(discord, model.User.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + if (model.VoiceState.IsSpecified) + { + Flags voiceStates = Flags.Normal; + if (model.VoiceState.Value.Mute) + voiceStates |= Flags.Muted; + if (model.VoiceState.Value.Deaf) + voiceStates |= Flags.Deafened; + if (model.VoiceState.Value.SelfMute) + voiceStates |= Flags.SelfMuted; + if (model.VoiceState.Value.SelfDeaf) + voiceStates |= Flags.SelfDeafened; + if (model.VoiceState.Value.Suppress) + voiceStates |= Flags.Suppressed; + _voiceStates = voiceStates; + } + User.Update(model.User); + if (model.Nickname.IsSpecified) + Nickname = model.Nickname.Value; + if (model.Volume.IsSpecified) + Volume = model.Volume.Value; + if (model.Mute.IsSpecified) + IsMuted2 = model.Mute.Value; + if (model.Pan.IsSpecified) + Pan = Pan.Create(model.Pan.Value); + } + + public override string ToString() => User.ToString(); + internal string DebuggerDisplay => $"{User} ({_voiceStates})"; + + string IVoiceState.VoiceSessionId { get { throw new NotSupportedException(); } } + IVoiceChannel IVoiceState.VoiceChannel { get { throw new NotSupportedException(); } } + } +} From 15e8ef06dc7eea5aeac697e71b3a325d5d88e581 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 23:18:45 -0300 Subject: [PATCH 070/102] Added RPC ChannelCreated, GuildCreated and GuildStatusUpdated events --- .../API/DiscordRpcApiClient.cs | 50 +++++++++-------- .../API/Rpc/ChannelCreatedEvent.cs | 14 +++++ .../API/Rpc/GuildCreatedEvent.cs | 12 ++++ .../DiscordRpcClient.Events.cs | 22 ++++++-- src/Discord.Net.Rpc/DiscordRpcClient.cs | 56 +++++++++++++++++-- .../Entities/Channels/RpcChannel.cs | 27 +++++++++ .../Entities/Guilds/RpcGuild.cs | 27 +++++---- .../Entities/Guilds/RpcGuildStatus.cs | 25 +++++++++ src/Discord.Net.Rpc/RpcGlobalEvent.cs | 8 +++ 9 files changed, 196 insertions(+), 45 deletions(-) create mode 100644 src/Discord.Net.Rpc/API/Rpc/ChannelCreatedEvent.cs create mode 100644 src/Discord.Net.Rpc/API/Rpc/GuildCreatedEvent.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs create mode 100644 src/Discord.Net.Rpc/RpcGlobalEvent.cs diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index 1d0fab516..540f9eb28 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -269,14 +269,14 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); return await SendRpcAsync("GET_GUILDS", null, options: options).ConfigureAwait(false); } - public async Task SendGetGuildAsync(ulong guildId, RequestOptions options = null) + public async Task SendGetGuildAsync(ulong guildId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var msg = new GetGuildParams { GuildId = guildId }; - return await SendRpcAsync("GET_GUILD", msg, options: options).ConfigureAwait(false); + return await SendRpcAsync("GET_GUILD", msg, options: options).ConfigureAwait(false); } public async Task SendGetChannelsAsync(ulong guildId, RequestOptions options = null) { @@ -287,14 +287,14 @@ namespace Discord.API }; return await SendRpcAsync("GET_CHANNELS", msg, options: options).ConfigureAwait(false); } - public async Task SendGetChannelAsync(ulong channelId, RequestOptions options = null) + public async Task SendGetChannelAsync(ulong channelId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var msg = new GetChannelParams { ChannelId = channelId }; - return await SendRpcAsync("GET_CHANNEL", msg, options: options).ConfigureAwait(false); + return await SendRpcAsync("GET_CHANNEL", msg, options: options).ConfigureAwait(false); } public async Task SendSetLocalVolumeAsync(int volume, RequestOptions options = null) @@ -306,59 +306,63 @@ namespace Discord.API }; return await SendRpcAsync("SET_LOCAL_VOLUME", msg, options: options).ConfigureAwait(false); } - public async Task SendSelectVoiceChannelAsync(ulong channelId, RequestOptions options = null) + public async Task SendSelectVoiceChannelAsync(ulong channelId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var msg = new SelectVoiceChannelParams { ChannelId = channelId }; - return await SendRpcAsync("SELECT_VOICE_CHANNEL", msg, options: options).ConfigureAwait(false); + return await SendRpcAsync("SELECT_VOICE_CHANNEL", msg, options: options).ConfigureAwait(false); } - public async Task SendChannelSubscribeAsync(string evt, ulong channelId, RequestOptions options = null) + public async Task SendGlobalSubscribeAsync(string evt, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new ChannelSubscriptionParams - { - ChannelId = channelId - }; + var msg = new object(); return await SendRpcAsync("SUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); } - public async Task SendChannelUnsubscribeAsync(string evt, ulong channelId, RequestOptions options = null) + public async Task SendGlobalUnsubscribeAsync(string evt, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new ChannelSubscriptionParams - { - ChannelId = channelId - }; + var msg = new object(); return await SendRpcAsync("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); } - public async Task SendGlobalSubscribeAsync(string evt, RequestOptions options = null) + public async Task SendGuildSubscribeAsync(string evt, ulong guildId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new ChannelSubscriptionParams + var msg = new GuildSubscriptionParams { + GuildId = guildId }; return await SendRpcAsync("SUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); } - - public async Task SendGuildSubscribeAsync(string evt, ulong guildId, RequestOptions options = null) + public async Task SendGuildUnsubscribeAsync(string evt, ulong guildId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var msg = new GuildSubscriptionParams { GuildId = guildId }; + return await SendRpcAsync("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); + } + + public async Task SendChannelSubscribeAsync(string evt, ulong channelId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + var msg = new ChannelSubscriptionParams + { + ChannelId = channelId + }; return await SendRpcAsync("SUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); } - public async Task SendGuildUnsubscribeAsync(string evt, ulong guildId, RequestOptions options = null) + public async Task SendChannelUnsubscribeAsync(string evt, ulong channelId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new GuildSubscriptionParams + var msg = new ChannelSubscriptionParams { - GuildId = guildId + ChannelId = channelId }; return await SendRpcAsync("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rpc/API/Rpc/ChannelCreatedEvent.cs b/src/Discord.Net.Rpc/API/Rpc/ChannelCreatedEvent.cs new file mode 100644 index 000000000..baa7caa6e --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/ChannelCreatedEvent.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class ChannelCreatedEvent + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("type")] + public ChannelType Type { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/GuildCreatedEvent.cs b/src/Discord.Net.Rpc/API/Rpc/GuildCreatedEvent.cs new file mode 100644 index 000000000..6cfaa862f --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/GuildCreatedEvent.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class GuildCreatedEvent + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs index 5639bdbae..7e7553ffb 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs @@ -25,13 +25,27 @@ namespace Discord.Rpc } private readonly AsyncEvent> _readyEvent = new AsyncEvent>(); + //Channel + public event Func ChannelCreated + { + add { _channelCreatedEvent.Add(value); } + remove { _channelCreatedEvent.Remove(value); } + } + private readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); + //Guild - public event Func GuildUpdated + public event Func GuildCreated + { + add { _guildCreatedEvent.Add(value); } + remove { _guildCreatedEvent.Remove(value); } + } + private readonly AsyncEvent> _guildCreatedEvent = new AsyncEvent>(); + public event Func GuildStatusUpdated { - add { _guildUpdatedEvent.Add(value); } - remove { _guildUpdatedEvent.Remove(value); } + add { _guildStatusUpdatedEvent.Add(value); } + remove { _guildStatusUpdatedEvent.Remove(value); } } - private readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _guildStatusUpdatedEvent = new AsyncEvent>(); //Voice public event Func VoiceStateCreated diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index a5fcd827a..4d3b92b5c 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -228,6 +228,18 @@ namespace Discord.Rpc return result.Code; } + public async Task SubscribeGlobal(params RpcGlobalEvent[] events) + { + Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); + for (int i = 0; i < events.Length; i++) + await ApiClient.SendGlobalSubscribeAsync(GetEventName(events[i])); + } + public async Task UnsubscribeGlobal(params RpcGlobalEvent[] events) + { + Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); + for (int i = 0; i < events.Length; i++) + await ApiClient.SendGlobalUnsubscribeAsync(GetEventName(events[i])); + } public async Task SubscribeGuild(ulong guildId, params RpcChannelEvent[] events) { Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); @@ -253,6 +265,16 @@ namespace Discord.Rpc await ApiClient.SendChannelUnsubscribeAsync(GetEventName(events[i]), channelId); } + private static string GetEventName(RpcGlobalEvent rpcEvent) + { + switch (rpcEvent) + { + case RpcGlobalEvent.ChannelCreated: return "CHANNEL_CREATE"; + case RpcGlobalEvent.GuildCreated: return "GUILD_CREATE"; + default: + throw new InvalidOperationException($"Unknown RPC Global Event: {rpcEvent}"); + } + } private static string GetEventName(RpcGuildEvent rpcEvent) { switch (rpcEvent) @@ -266,14 +288,14 @@ namespace Discord.Rpc { switch (rpcEvent) { - case RpcChannelEvent.VoiceStateCreate: return "VOICE_STATE_CREATE"; - case RpcChannelEvent.VoiceStateUpdate: return "VOICE_STATE_UPDATE"; - case RpcChannelEvent.VoiceStateDelete: return "VOICE_STATE_DELETE"; - case RpcChannelEvent.SpeakingStart: return "SPEAKING_START"; - case RpcChannelEvent.SpeakingStop: return "SPEAKING_STOP"; case RpcChannelEvent.MessageCreate: return "MESSAGE_CREATE"; case RpcChannelEvent.MessageUpdate: return "MESSAGE_UPDATE"; case RpcChannelEvent.MessageDelete: return "MESSAGE_DELETE"; + case RpcChannelEvent.SpeakingStart: return "SPEAKING_START"; + case RpcChannelEvent.SpeakingStop: return "SPEAKING_STOP"; + case RpcChannelEvent.VoiceStateCreate: return "VOICE_STATE_CREATE"; + case RpcChannelEvent.VoiceStateUpdate: return "VOICE_STATE_UPDATE"; + case RpcChannelEvent.VoiceStateDelete: return "VOICE_STATE_DELETE"; default: throw new InvalidOperationException($"Unknown RPC Channel Event: {rpcEvent}"); } @@ -321,12 +343,34 @@ namespace Discord.Rpc } break; + //Channels + case "CHANNEL_CREATE": + { + await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); + var channel = RpcChannel.Create(data); + + await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); + } + break; + //Guilds + case "GUILD_CREATE": + { + await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); + var guild = RpcGuild.Create(data); + + await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false); + } + break; case "GUILD_STATUS": { await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); + var guildStatus = RpcGuildStatus.Create(data); - await _guildUpdatedEvent.InvokeAsync().ConfigureAwait(false); + await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false); } break; diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs new file mode 100644 index 000000000..dbaa413fc --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs @@ -0,0 +1,27 @@ +using Model = Discord.API.Rpc.ChannelCreatedEvent; + +namespace Discord.Rpc +{ + public class RpcChannel + { + public ulong Id { get; } + public string Name { get; set; } + public ChannelType Type { get; set; } + + internal RpcChannel(ulong id) + { + Id = id; + } + internal static RpcChannel Create(Model model) + { + var entity = new RpcChannel(model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Name = model.Name; + Type = model.Type; + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs index 3c0216dc9..6a16a721b 100644 --- a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs +++ b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs @@ -1,22 +1,25 @@ -namespace Discord.Rpc +using Model = Discord.API.Rpc.GuildCreatedEvent; + +namespace Discord.Rpc { - /*internal class RemoteUserGuild : RpcEntity, IRemoteUserGuild, ISnowflakeEntity + public class RpcGuild { public ulong Id { get; } - public DiscordRestClient Discord { get; } - public string Name { get; private set; } - - public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); + public string Name { get; set; } - internal RemoteUserGuild(DiscordRestClient discord, Model model) + internal RpcGuild(ulong id) + { + Id = id; + } + internal static RpcGuild Create(Model model) { - Id = model.Id; - Discord = discord; - Update(model); + var entity = new RpcGuild(model.Id); + entity.Update(model); + return entity; } - public void Update(Model model) + internal void Update(Model model) { Name = model.Name; } - }*/ + } } diff --git a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs new file mode 100644 index 000000000..f8095a18e --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs @@ -0,0 +1,25 @@ +using Model = Discord.API.Rpc.GuildStatusEvent; + +namespace Discord.Rpc +{ + public class RpcGuildStatus + { + public RpcGuild Guild { get; } + public int Online { get; private set; } + + internal RpcGuildStatus(ulong guildId) + { + Guild = new RpcGuild(guildId); + } + internal static RpcGuildStatus Create(Model model) + { + var entity = new RpcGuildStatus(model.Guild.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Online = model.Online; + } + } +} diff --git a/src/Discord.Net.Rpc/RpcGlobalEvent.cs b/src/Discord.Net.Rpc/RpcGlobalEvent.cs new file mode 100644 index 000000000..989e3f52e --- /dev/null +++ b/src/Discord.Net.Rpc/RpcGlobalEvent.cs @@ -0,0 +1,8 @@ +namespace Discord.Rpc +{ + public enum RpcGlobalEvent + { + ChannelCreated, + GuildCreated + } +} From 79f04a5b30c3e0b1a40df436bad61919ed181884 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 7 Oct 2016 23:59:19 -0300 Subject: [PATCH 071/102] Added VoiceSettingsUpdated RPC event --- src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs | 12 ++++ .../API/Rpc/VoiceDeviceSettings.cs | 14 +++++ src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs | 18 ++++++ src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs | 26 ++++++++ src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs | 15 +++++ .../DiscordRpcClient.Events.cs | 7 +++ src/Discord.Net.Rpc/DiscordRpcClient.cs | 10 ++++ src/Discord.Net.Rpc/Entities/VoiceDevice.cs | 20 +++++++ src/Discord.Net.Rpc/Entities/VoiceSettings.cs | 60 +++++++++++++++++++ src/Discord.Net.Rpc/Entities/VoiceShortcut.cs | 22 +++++++ .../Entities/VoiceShortcutType.cs | 10 ++++ src/Discord.Net.Rpc/RpcGlobalEvent.cs | 3 +- 12 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs create mode 100644 src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs create mode 100644 src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs create mode 100644 src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs create mode 100644 src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs create mode 100644 src/Discord.Net.Rpc/Entities/VoiceDevice.cs create mode 100644 src/Discord.Net.Rpc/Entities/VoiceSettings.cs create mode 100644 src/Discord.Net.Rpc/Entities/VoiceShortcut.cs create mode 100644 src/Discord.Net.Rpc/Entities/VoiceShortcutType.cs diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs new file mode 100644 index 000000000..4dc99d4cd --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class VoiceDevice + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs new file mode 100644 index 000000000..9bad1dd6d --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class VoiceDeviceSettings + { + [JsonProperty("device_id")] + public string DeviceId { get; set; } + [JsonProperty("volume")] + public float Volume { get; set; } + [JsonProperty("available_devices")] + public VoiceDevice[] AvailableDevices { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs new file mode 100644 index 000000000..f9b650fb5 --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class VoiceMode + { + [JsonProperty("type")] + public string Type { get; set; } + [JsonProperty("auto_threshold")] + public bool AutoThreshold { get; set; } + [JsonProperty("threshold")] + public float Threshold { get; set; } + [JsonProperty("shortcut")] + public VoiceShortcut[] Shortcut { get; set; } + [JsonProperty("delay")] + public float Delay { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs new file mode 100644 index 000000000..817c75cfe --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs @@ -0,0 +1,26 @@ +#pragma warning disable CS1591 + +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class VoiceSettings + { + [JsonProperty("input")] + public VoiceDeviceSettings Input { get; set; } + [JsonProperty("output")] + public VoiceDeviceSettings Output { get; set; } + [JsonProperty("mode")] + public VoiceMode Mode { get; set; } + [JsonProperty("automatic_gain_control")] + public bool AutomaticGainControl { get; set; } + [JsonProperty("echo_cancellation")] + public bool EchoCancellation { get; set; } + [JsonProperty("noise_suppression")] + public bool NoiseSuppression { get; set; } + [JsonProperty("qos")] + public bool QualityOfService { get; set; } + [JsonProperty("silence_warning")] + public bool SilenceWarning { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs new file mode 100644 index 000000000..e090071ee --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs @@ -0,0 +1,15 @@ +using Discord.Rpc; +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class VoiceShortcut + { + [JsonProperty("type")] + public VoiceShortcutType Type { get; set; } + [JsonProperty("code")] + public int Code { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs index 7e7553ffb..a966ef149 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs @@ -82,6 +82,13 @@ namespace Discord.Rpc } private readonly AsyncEvent> _speakingStoppedEvent = new AsyncEvent>(); + public event Func VoiceSettingsUpdated + { + add { _voiceSettingsUpdated.Add(value); } + remove { _voiceSettingsUpdated.Remove(value); } + } + private readonly AsyncEvent> _voiceSettingsUpdated = new AsyncEvent>(); + //Messages public event Func MessageReceived { diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 4d3b92b5c..1d67cc8d0 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -271,6 +271,7 @@ namespace Discord.Rpc { case RpcGlobalEvent.ChannelCreated: return "CHANNEL_CREATE"; case RpcGlobalEvent.GuildCreated: return "GUILD_CREATE"; + case RpcGlobalEvent.VoiceSettingsUpdated: return "VOICE_SETTINGS_UPDATE"; default: throw new InvalidOperationException($"Unknown RPC Global Event: {rpcEvent}"); } @@ -419,6 +420,15 @@ namespace Discord.Rpc await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); } break; + case "VOICE_SETTINGS_UPDATE": + { + await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false); + var data = (payload.Value as JToken).ToObject(_serializer); + var settings = VoiceSettings.Create(data); + + await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false); + } + break; //Messages case "MESSAGE_CREATE": diff --git a/src/Discord.Net.Rpc/Entities/VoiceDevice.cs b/src/Discord.Net.Rpc/Entities/VoiceDevice.cs new file mode 100644 index 000000000..328dd83f6 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/VoiceDevice.cs @@ -0,0 +1,20 @@ +using Model = Discord.API.Rpc.VoiceDevice; + +namespace Discord.Rpc +{ + public struct VoiceDevice + { + public string Id { get; } + public string Name { get; } + + internal VoiceDevice(string id, string name) + { + Id = id; + Name = name; + } + internal static VoiceDevice Create(Model model) + { + return new VoiceDevice(model.Id, model.Name); + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/VoiceSettings.cs b/src/Discord.Net.Rpc/Entities/VoiceSettings.cs new file mode 100644 index 000000000..48e5538fd --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/VoiceSettings.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Model = Discord.API.Rpc.VoiceSettings; + +namespace Discord.Rpc +{ + public class VoiceSettings + { + public string InputDeviceId { get; private set; } + public float InputVolume { get; private set; } + public IReadOnlyCollection AvailableInputDevices { get; private set; } + + public string OutputDeviceId { get; private set; } + public float OutputVolume { get; private set; } + public IReadOnlyCollection AvailableOutputDevices { get; private set; } + + public bool AutomaticGainControl { get; private set; } + public bool EchoCancellation { get; private set; } + public bool NoiseSuppression { get; private set; } + public bool QualityOfService { get; private set; } + public bool SilenceWarning { get; private set; } + + public string ActivationMode { get; private set; } + public bool AutoThreshold { get; private set; } + public float Threshold { get; private set; } + public IReadOnlyCollection Shortcuts { get; private set; } + public float Delay { get; private set; } + + internal VoiceSettings() { } + internal static VoiceSettings Create(Model model) + { + var entity = new VoiceSettings(); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + AutomaticGainControl = model.AutomaticGainControl; + EchoCancellation = model.EchoCancellation; + NoiseSuppression = model.NoiseSuppression; + QualityOfService = model.QualityOfService; + SilenceWarning = model.SilenceWarning; + + InputDeviceId = model.Input.DeviceId; + InputVolume = model.Input.Volume; + AvailableInputDevices = model.Input.AvailableDevices.Select(x => VoiceDevice.Create(x)).ToImmutableArray(); + + OutputDeviceId = model.Output.DeviceId; + OutputVolume = model.Output.Volume; + AvailableInputDevices = model.Output.AvailableDevices.Select(x => VoiceDevice.Create(x)).ToImmutableArray(); + + ActivationMode = model.Mode.Type; + AutoThreshold = model.Mode.AutoThreshold; + Threshold = model.Mode.Threshold; + Shortcuts = model.Mode.Shortcut.Select(x => VoiceShortcut.Create(x)).ToImmutableArray(); + Delay = model.Mode.Delay; + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs b/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs new file mode 100644 index 000000000..131c06f1c --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs @@ -0,0 +1,22 @@ +using Model = Discord.API.Rpc.VoiceShortcut; + +namespace Discord.Rpc +{ + public struct VoiceShortcut + { + public VoiceShortcutType Type { get; } + public int Code { get; } + public string Name { get; } + + internal VoiceShortcut(VoiceShortcutType type, int code, string name) + { + Type = type; + Code = code; + Name = name; + } + internal static VoiceShortcut Create(Model model) + { + return new VoiceShortcut(model.Type, model.Code, model.Name); + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/VoiceShortcutType.cs b/src/Discord.Net.Rpc/Entities/VoiceShortcutType.cs new file mode 100644 index 000000000..f3b82804e --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/VoiceShortcutType.cs @@ -0,0 +1,10 @@ +namespace Discord.Rpc +{ + public enum VoiceShortcutType + { + KeyboardKey = 0, + MouseButton = 1, + KeyboardModifierKey = 2, + GamepadButton = 3 + } +} diff --git a/src/Discord.Net.Rpc/RpcGlobalEvent.cs b/src/Discord.Net.Rpc/RpcGlobalEvent.cs index 989e3f52e..673eaed5d 100644 --- a/src/Discord.Net.Rpc/RpcGlobalEvent.cs +++ b/src/Discord.Net.Rpc/RpcGlobalEvent.cs @@ -3,6 +3,7 @@ public enum RpcGlobalEvent { ChannelCreated, - GuildCreated + GuildCreated, + VoiceSettingsUpdated } } From 4a8ebff77554c0410edcf73064837af5b3ad446f Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 00:05:45 -0300 Subject: [PATCH 072/102] Added ToString/DebuggerDisplay to new RPC classes --- src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs | 7 ++++++- src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs | 7 ++++++- src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs | 7 ++++++- src/Discord.Net.Rpc/Entities/Users/Pan.cs | 7 ++++++- src/Discord.Net.Rpc/Entities/VoiceDevice.cs | 7 ++++++- src/Discord.Net.Rpc/Entities/VoiceShortcut.cs | 7 ++++++- 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs index dbaa413fc..fc4d6c012 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.Rpc.ChannelCreatedEvent; +using System.Diagnostics; +using Model = Discord.API.Rpc.ChannelCreatedEvent; namespace Discord.Rpc { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RpcChannel { public ulong Id { get; } @@ -23,5 +25,8 @@ namespace Discord.Rpc Name = model.Name; Type = model.Type; } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}, {Type})"; } } diff --git a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs index 6a16a721b..c8ee39ffc 100644 --- a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs +++ b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.Rpc.GuildCreatedEvent; +using System.Diagnostics; +using Model = Discord.API.Rpc.GuildCreatedEvent; namespace Discord.Rpc { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RpcGuild { public ulong Id { get; } @@ -21,5 +23,8 @@ namespace Discord.Rpc { Name = model.Name; } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; } } diff --git a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs index f8095a18e..4b74249cc 100644 --- a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs +++ b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.Rpc.GuildStatusEvent; +using System.Diagnostics; +using Model = Discord.API.Rpc.GuildStatusEvent; namespace Discord.Rpc { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RpcGuildStatus { public RpcGuild Guild { get; } @@ -21,5 +23,8 @@ namespace Discord.Rpc { Online = model.Online; } + + public override string ToString() => Guild.Name; + private string DebuggerDisplay => $"{Guild.Name} ({Guild.Id}, {Online} Online)"; } } diff --git a/src/Discord.Net.Rpc/Entities/Users/Pan.cs b/src/Discord.Net.Rpc/Entities/Users/Pan.cs index 8a64bddf3..2db6cdb1e 100644 --- a/src/Discord.Net.Rpc/Entities/Users/Pan.cs +++ b/src/Discord.Net.Rpc/Entities/Users/Pan.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.Rpc.Pan; +using System.Diagnostics; +using Model = Discord.API.Rpc.Pan; namespace Discord.Rpc { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Pan { public float Left { get; } @@ -16,5 +18,8 @@ namespace Discord.Rpc { return new Pan(model.Left, model.Right); } + + public override string ToString() => $"Left = {Left}, Right = {Right}"; + private string DebuggerDisplay => $"Left = {Left}, Right = {Right}"; } } diff --git a/src/Discord.Net.Rpc/Entities/VoiceDevice.cs b/src/Discord.Net.Rpc/Entities/VoiceDevice.cs index 328dd83f6..34a718adc 100644 --- a/src/Discord.Net.Rpc/Entities/VoiceDevice.cs +++ b/src/Discord.Net.Rpc/Entities/VoiceDevice.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.Rpc.VoiceDevice; +using System.Diagnostics; +using Model = Discord.API.Rpc.VoiceDevice; namespace Discord.Rpc { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct VoiceDevice { public string Id { get; } @@ -16,5 +18,8 @@ namespace Discord.Rpc { return new VoiceDevice(model.Id, model.Name); } + + public override string ToString() => $"{Name}"; + internal string DebuggerDisplay => $"{Name} ({Id})"; } } diff --git a/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs b/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs index 131c06f1c..d71e30ee8 100644 --- a/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs +++ b/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.Rpc.VoiceShortcut; +using System.Diagnostics; +using Model = Discord.API.Rpc.VoiceShortcut; namespace Discord.Rpc { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct VoiceShortcut { public VoiceShortcutType Type { get; } @@ -18,5 +20,8 @@ namespace Discord.Rpc { return new VoiceShortcut(model.Type, model.Code, model.Name); } + + public override string ToString() => $"{Name}"; + internal string DebuggerDisplay => $"{Name} ({Code}, {Type})"; } } From f584bd6e28d1f0b2f357d4cdcf994ef0a22319e3 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 00:33:04 -0300 Subject: [PATCH 073/102] Added RPC Get/Set VoiceSettings --- .../API/DiscordRpcApiClient.cs | 17 ++++-- .../API/Rpc/VoiceDeviceSettings.cs | 6 +-- src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs | 10 ++-- src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs | 10 ++-- src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs | 6 +-- src/Discord.Net.Rpc/DiscordRpcClient.cs | 15 ++++++ src/Discord.Net.Rpc/Entities/VoiceSettings.cs | 54 ++++++++++++------- src/Discord.Net.Rpc/Entities/VoiceShortcut.cs | 2 +- 8 files changed, 80 insertions(+), 40 deletions(-) diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index 540f9eb28..10fd054f5 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -319,14 +319,12 @@ namespace Discord.API public async Task SendGlobalSubscribeAsync(string evt, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new object(); - return await SendRpcAsync("SUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); + return await SendRpcAsync("SUBSCRIBE", null, evt: evt, options: options).ConfigureAwait(false); } public async Task SendGlobalUnsubscribeAsync(string evt, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new object(); - return await SendRpcAsync("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); + return await SendRpcAsync("UNSUBSCRIBE", null, evt: evt, options: options).ConfigureAwait(false); } public async Task SendGuildSubscribeAsync(string evt, ulong guildId, RequestOptions options = null) @@ -367,6 +365,17 @@ namespace Discord.API return await SendRpcAsync("UNSUBSCRIBE", msg, evt: evt, options: options).ConfigureAwait(false); } + public async Task GetVoiceSettingsAsync(RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + return await SendRpcAsync("GET_VOICE_SETTINGS", null, options: options).ConfigureAwait(false); + } + public async Task SetVoiceSettingsAsync(API.Rpc.VoiceSettings settings, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + return await SendRpcAsync("SET_VOICE_SETTINGS", settings, options: options).ConfigureAwait(false); + } + private bool ProcessMessage(API.Rpc.RpcFrame msg) { RpcRequest requestTracker; diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs index 9bad1dd6d..38473c803 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs @@ -5,10 +5,10 @@ namespace Discord.API.Rpc public class VoiceDeviceSettings { [JsonProperty("device_id")] - public string DeviceId { get; set; } + public Optional DeviceId { get; set; } [JsonProperty("volume")] - public float Volume { get; set; } + public Optional Volume { get; set; } [JsonProperty("available_devices")] - public VoiceDevice[] AvailableDevices { get; set; } + public Optional AvailableDevices { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs index f9b650fb5..a502cc960 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs @@ -5,14 +5,14 @@ namespace Discord.API.Rpc public class VoiceMode { [JsonProperty("type")] - public string Type { get; set; } + public Optional Type { get; set; } [JsonProperty("auto_threshold")] - public bool AutoThreshold { get; set; } + public Optional AutoThreshold { get; set; } [JsonProperty("threshold")] - public float Threshold { get; set; } + public Optional Threshold { get; set; } [JsonProperty("shortcut")] - public VoiceShortcut[] Shortcut { get; set; } + public Optional Shortcut { get; set; } [JsonProperty("delay")] - public float Delay { get; set; } + public Optional Delay { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs index 817c75cfe..c3268a719 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs @@ -13,14 +13,14 @@ namespace Discord.API.Rpc [JsonProperty("mode")] public VoiceMode Mode { get; set; } [JsonProperty("automatic_gain_control")] - public bool AutomaticGainControl { get; set; } + public Optional AutomaticGainControl { get; set; } [JsonProperty("echo_cancellation")] - public bool EchoCancellation { get; set; } + public Optional EchoCancellation { get; set; } [JsonProperty("noise_suppression")] - public bool NoiseSuppression { get; set; } + public Optional NoiseSuppression { get; set; } [JsonProperty("qos")] - public bool QualityOfService { get; set; } + public Optional QualityOfService { get; set; } [JsonProperty("silence_warning")] - public bool SilenceWarning { get; set; } + public Optional SilenceWarning { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs index e090071ee..5b0939d79 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs @@ -6,10 +6,10 @@ namespace Discord.API.Rpc public class VoiceShortcut { [JsonProperty("type")] - public VoiceShortcutType Type { get; set; } + public Optional Type { get; set; } [JsonProperty("code")] - public int Code { get; set; } + public Optional Code { get; set; } [JsonProperty("name")] - public string Name { get; set; } + public Optional Name { get; set; } } } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 1d67cc8d0..708e18878 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -265,6 +265,21 @@ namespace Discord.Rpc await ApiClient.SendChannelUnsubscribeAsync(GetEventName(events[i]), channelId); } + public async Task GetVoiceSettingsAsync() + { + var model = await ApiClient.GetVoiceSettingsAsync().ConfigureAwait(false); + return VoiceSettings.Create(model); + } + public async Task SetVoiceSettingsAsync(Action func) + { + var settings = new API.Rpc.VoiceSettings(); + settings.Input = new VoiceDeviceSettings(); + settings.Output = new VoiceDeviceSettings(); + settings.Mode = new VoiceMode(); + func(settings); + await ApiClient.SetVoiceSettingsAsync(settings).ConfigureAwait(false); + } + private static string GetEventName(RpcGlobalEvent rpcEvent) { switch (rpcEvent) diff --git a/src/Discord.Net.Rpc/Entities/VoiceSettings.cs b/src/Discord.Net.Rpc/Entities/VoiceSettings.cs index 48e5538fd..2e8d6e74c 100644 --- a/src/Discord.Net.Rpc/Entities/VoiceSettings.cs +++ b/src/Discord.Net.Rpc/Entities/VoiceSettings.cs @@ -36,25 +36,41 @@ namespace Discord.Rpc } internal void Update(Model model) { - AutomaticGainControl = model.AutomaticGainControl; - EchoCancellation = model.EchoCancellation; - NoiseSuppression = model.NoiseSuppression; - QualityOfService = model.QualityOfService; - SilenceWarning = model.SilenceWarning; - - InputDeviceId = model.Input.DeviceId; - InputVolume = model.Input.Volume; - AvailableInputDevices = model.Input.AvailableDevices.Select(x => VoiceDevice.Create(x)).ToImmutableArray(); - - OutputDeviceId = model.Output.DeviceId; - OutputVolume = model.Output.Volume; - AvailableInputDevices = model.Output.AvailableDevices.Select(x => VoiceDevice.Create(x)).ToImmutableArray(); - - ActivationMode = model.Mode.Type; - AutoThreshold = model.Mode.AutoThreshold; - Threshold = model.Mode.Threshold; - Shortcuts = model.Mode.Shortcut.Select(x => VoiceShortcut.Create(x)).ToImmutableArray(); - Delay = model.Mode.Delay; + if (model.AutomaticGainControl.IsSpecified) + AutomaticGainControl = model.AutomaticGainControl.Value; + if (model.EchoCancellation.IsSpecified) + EchoCancellation = model.EchoCancellation.Value; + if (model.NoiseSuppression.IsSpecified) + NoiseSuppression = model.NoiseSuppression.Value; + if (model.QualityOfService.IsSpecified) + QualityOfService = model.QualityOfService.Value; + if (model.SilenceWarning.IsSpecified) + SilenceWarning = model.SilenceWarning.Value; + + if (model.Input.DeviceId.IsSpecified) + InputDeviceId = model.Input.DeviceId.Value; + if (model.Input.Volume.IsSpecified) + InputVolume = model.Input.Volume.Value; + if (model.Input.AvailableDevices.IsSpecified) + AvailableInputDevices = model.Input.AvailableDevices.Value.Select(x => VoiceDevice.Create(x)).ToImmutableArray(); + + if (model.Output.DeviceId.IsSpecified) + OutputDeviceId = model.Output.DeviceId.Value; + if (model.Output.Volume.IsSpecified) + OutputVolume = model.Output.Volume.Value; + if (model.Output.AvailableDevices.IsSpecified) + AvailableInputDevices = model.Output.AvailableDevices.Value.Select(x => VoiceDevice.Create(x)).ToImmutableArray(); + + if (model.Mode.Type.IsSpecified) + ActivationMode = model.Mode.Type.Value; + if (model.Mode.AutoThreshold.IsSpecified) + AutoThreshold = model.Mode.AutoThreshold.Value; + if (model.Mode.Threshold.IsSpecified) + Threshold = model.Mode.Threshold.Value; + if (model.Mode.Shortcut.IsSpecified) + Shortcuts = model.Mode.Shortcut.Value.Select(x => VoiceShortcut.Create(x)).ToImmutableArray(); + if (model.Mode.Delay.IsSpecified) + Delay = model.Mode.Delay.Value; } } } diff --git a/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs b/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs index d71e30ee8..93ef21804 100644 --- a/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs +++ b/src/Discord.Net.Rpc/Entities/VoiceShortcut.cs @@ -18,7 +18,7 @@ namespace Discord.Rpc } internal static VoiceShortcut Create(Model model) { - return new VoiceShortcut(model.Type, model.Code, model.Name); + return new VoiceShortcut(model.Type.Value, model.Code.Value, model.Name.Value); } public override string ToString() => $"{Name}"; From 16c67e79e99c63671010170ba7dcebdde773f7da Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 02:37:04 -0300 Subject: [PATCH 074/102] Added RPC channel and guild entities, Get and Select methods --- .../Net/WebSockets/DefaultWebSocketClient.cs | 3 + .../API/DiscordRpcApiClient.cs | 37 +++--- src/Discord.Net.Rpc/API/Rpc/Channel.cs | 34 +++++ ...annelCreatedEvent.cs => ChannelSummary.cs} | 2 +- ...iceStateEvent.cs => ExtendedVoiceState.cs} | 2 +- .../API/Rpc/GetChannelsResponse.cs | 3 +- .../API/Rpc/GetGuildsParams.cs | 1 + .../API/Rpc/GetGuildsResponse.cs | 2 +- .../API/Rpc/{RpcUserGuild.cs => Guild.cs} | 7 +- src/Discord.Net.Rpc/API/Rpc/GuildMember.cs | 15 +++ .../{GuildCreatedEvent.cs => GuildSummary.cs} | 2 +- .../API/Rpc/{RpcMessage.cs => Message.cs} | 2 +- src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs | 2 +- src/Discord.Net.Rpc/API/Rpc/RpcChannel.cs | 11 -- src/Discord.Net.Rpc/API/Rpc/RpcGuild.cs | 13 -- ...hannelParams.cs => SelectChannelParams.cs} | 4 +- .../API/Rpc/UserVoiceSettings.cs | 18 +++ .../DiscordRpcClient.Events.cs | 8 +- src/Discord.Net.Rpc/DiscordRpcClient.cs | 74 ++++++++++- .../Entities/Channels/IRpcAudioChannel.cs | 9 ++ .../Entities/Channels/IRpcMessageChannel.cs | 9 ++ .../Entities/Channels/IRpcPrivateChannel.cs | 6 + .../Entities/Channels/RpcChannel.cs | 46 ++++--- .../Entities/Channels/RpcChannelSummary.cs | 32 +++++ .../Entities/Channels/RpcDMChannel.cs | 124 ++++++++++++++++++ .../Entities/Channels/RpcGroupChannel.cs | 123 +++++++++++++++++ .../Entities/Channels/RpcGuildChannel.cs | 94 +++++++++++++ .../Entities/Channels/RpcTextChannel.cs | 113 ++++++++++++++++ .../Entities/Channels/RpcVoiceChannel.cs | 49 +++++++ .../Entities/Guilds/RpcGuild.cs | 24 ++-- .../Entities/Guilds/RpcGuildStatus.cs | 4 +- .../Entities/Guilds/RpcGuildSummary.cs | 30 +++++ .../Entities/Messages/RpcMessage.cs | 2 +- .../Entities/Messages/RpcSystemMessage.cs | 2 +- .../Entities/Messages/RpcUserMessage.cs | 2 +- .../Entities/Users/RpcGuildUser.cs | 29 ++++ src/Discord.Net.Rpc/Entities/Users/RpcUser.cs | 8 +- .../Entities/Users/RpcVoiceState.cs | 2 +- .../Entities/Channels/SocketChannel.cs | 1 - 39 files changed, 848 insertions(+), 101 deletions(-) create mode 100644 src/Discord.Net.Rpc/API/Rpc/Channel.cs rename src/Discord.Net.Rpc/API/Rpc/{ChannelCreatedEvent.cs => ChannelSummary.cs} (88%) rename src/Discord.Net.Rpc/API/Rpc/{VoiceStateEvent.cs => ExtendedVoiceState.cs} (94%) rename src/Discord.Net.Rpc/API/Rpc/{RpcUserGuild.cs => Guild.cs} (50%) create mode 100644 src/Discord.Net.Rpc/API/Rpc/GuildMember.cs rename src/Discord.Net.Rpc/API/Rpc/{GuildCreatedEvent.cs => GuildSummary.cs} (85%) rename src/Discord.Net.Rpc/API/Rpc/{RpcMessage.cs => Message.cs} (90%) delete mode 100644 src/Discord.Net.Rpc/API/Rpc/RpcChannel.cs delete mode 100644 src/Discord.Net.Rpc/API/Rpc/RpcGuild.cs rename src/Discord.Net.Rpc/API/Rpc/{SelectVoiceChannelParams.cs => SelectChannelParams.cs} (60%) create mode 100644 src/Discord.Net.Rpc/API/Rpc/UserVoiceSettings.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/IRpcAudioChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/IRpcMessageChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/IRpcPrivateChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/RpcChannelSummary.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Channels/RpcVoiceChannel.cs create mode 100644 src/Discord.Net.Rpc/Entities/Guilds/RpcGuildSummary.cs create mode 100644 src/Discord.Net.Rpc/Entities/Users/RpcGuildUser.cs diff --git a/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs index 51464efd3..2e4a90ad8 100644 --- a/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs +++ b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs @@ -154,6 +154,7 @@ namespace Discord.Net.WebSockets while (!cancelToken.IsCancellationRequested) { WebSocketReceiveResult socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); + System.Diagnostics.Debug.WriteLine("Got " + socketResult.Count); byte[] result; int resultCount; @@ -193,6 +194,7 @@ namespace Discord.Net.WebSockets result = buffer.Array; } + System.Diagnostics.Debug.WriteLine("Start"); if (socketResult.MessageType == WebSocketMessageType.Text) { string text = Encoding.UTF8.GetString(result, 0, resultCount); @@ -200,6 +202,7 @@ namespace Discord.Net.WebSockets } else await BinaryMessage(result, 0, resultCount).ConfigureAwait(false); + System.Diagnostics.Debug.WriteLine("Stop"); } } catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index 10fd054f5..67ead6f83 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -269,14 +269,14 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); return await SendRpcAsync("GET_GUILDS", null, options: options).ConfigureAwait(false); } - public async Task SendGetGuildAsync(ulong guildId, RequestOptions options = null) + public async Task SendGetGuildAsync(ulong guildId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var msg = new GetGuildParams { GuildId = guildId }; - return await SendRpcAsync("GET_GUILD", msg, options: options).ConfigureAwait(false); + return await SendRpcAsync("GET_GUILD", msg, options: options).ConfigureAwait(false); } public async Task SendGetChannelsAsync(ulong guildId, RequestOptions options = null) { @@ -287,33 +287,34 @@ namespace Discord.API }; return await SendRpcAsync("GET_CHANNELS", msg, options: options).ConfigureAwait(false); } - public async Task SendGetChannelAsync(ulong channelId, RequestOptions options = null) + public async Task SendGetChannelAsync(ulong channelId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var msg = new GetChannelParams { ChannelId = channelId }; - return await SendRpcAsync("GET_CHANNEL", msg, options: options).ConfigureAwait(false); + return await SendRpcAsync("GET_CHANNEL", msg, options: options).ConfigureAwait(false); } - - public async Task SendSetLocalVolumeAsync(int volume, RequestOptions options = null) + + public async Task SendSelectTextChannelAsync(ulong channelId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new SetLocalVolumeParams + var msg = new SelectChannelParams { - Volume = volume + ChannelId = channelId }; - return await SendRpcAsync("SET_LOCAL_VOLUME", msg, options: options).ConfigureAwait(false); + return await SendRpcAsync("SELECT_TEXT_CHANNEL", msg, options: options).ConfigureAwait(false); } - public async Task SendSelectVoiceChannelAsync(ulong channelId, RequestOptions options = null) + public async Task SendSelectVoiceChannelAsync(ulong channelId, bool force = false, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new SelectVoiceChannelParams + var msg = new SelectChannelParams { - ChannelId = channelId + ChannelId = channelId, + Force = force }; - return await SendRpcAsync("SELECT_VOICE_CHANNEL", msg, options: options).ConfigureAwait(false); + return await SendRpcAsync("SELECT_VOICE_CHANNEL", msg, options: options).ConfigureAwait(false); } public async Task SendGlobalSubscribeAsync(string evt, RequestOptions options = null) @@ -370,10 +371,16 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); return await SendRpcAsync("GET_VOICE_SETTINGS", null, options: options).ConfigureAwait(false); } - public async Task SetVoiceSettingsAsync(API.Rpc.VoiceSettings settings, RequestOptions options = null) + public async Task SetVoiceSettingsAsync(API.Rpc.VoiceSettings settings, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + await SendRpcAsync("SET_VOICE_SETTINGS", settings, options: options).ConfigureAwait(false); + } + public async Task SetUserVoiceSettingsAsync(ulong userId, API.Rpc.UserVoiceSettings settings, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendRpcAsync("SET_VOICE_SETTINGS", settings, options: options).ConfigureAwait(false); + settings.UserId = userId; + await SendRpcAsync("SET_USER_VOICE_SETTINGS", settings, options: options).ConfigureAwait(false); } private bool ProcessMessage(API.Rpc.RpcFrame msg) diff --git a/src/Discord.Net.Rpc/API/Rpc/Channel.cs b/src/Discord.Net.Rpc/API/Rpc/Channel.cs new file mode 100644 index 000000000..1b8f3775c --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/Channel.cs @@ -0,0 +1,34 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class Channel + { + //Shared + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("type")] + public ChannelType Type { 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; } + + //IMessageChannel + [JsonProperty("messages")] + public Message[] Messages { get; set; } + + //VoiceChannel + [JsonProperty("bitrate")] + public Optional Bitrate { get; set; } + [JsonProperty("user_limit")] + public Optional UserLimit { get; set; } + [JsonProperty("voice_states")] + public ExtendedVoiceState[] VoiceStates { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/ChannelCreatedEvent.cs b/src/Discord.Net.Rpc/API/Rpc/ChannelSummary.cs similarity index 88% rename from src/Discord.Net.Rpc/API/Rpc/ChannelCreatedEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/ChannelSummary.cs index baa7caa6e..34acd049b 100644 --- a/src/Discord.Net.Rpc/API/Rpc/ChannelCreatedEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/ChannelSummary.cs @@ -2,7 +2,7 @@ namespace Discord.API.Rpc { - public class ChannelCreatedEvent + public class ChannelSummary { [JsonProperty("id")] public ulong Id { get; set; } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs b/src/Discord.Net.Rpc/API/Rpc/ExtendedVoiceState.cs similarity index 94% rename from src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/ExtendedVoiceState.cs index 11359fb8f..032914f0f 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceStateEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/ExtendedVoiceState.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Rpc { - public class VoiceStateEvent + public class ExtendedVoiceState { [JsonProperty("user")] public User User { get; set; } diff --git a/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs b/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs index 5c428d7ba..e105341a1 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs @@ -1,11 +1,12 @@ #pragma warning disable CS1591 using Newtonsoft.Json; +using System.Collections.Generic; namespace Discord.API.Rpc { public class GetChannelsResponse { [JsonProperty("channels")] - public RpcChannel[] Channels { get; set; } + public IReadOnlyCollection Channels { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GetGuildsParams.cs b/src/Discord.Net.Rpc/API/Rpc/GetGuildsParams.cs index 9e45db44a..a1ff5f210 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GetGuildsParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GetGuildsParams.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 + namespace Discord.API.Rpc { public class GetGuildsParams diff --git a/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs b/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs index df7739709..e69bedeae 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs @@ -6,6 +6,6 @@ namespace Discord.API.Rpc public class GetGuildsResponse { [JsonProperty("guilds")] - public RpcUserGuild[] Guilds { get; set; } + public GuildSummary[] Guilds { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/RpcUserGuild.cs b/src/Discord.Net.Rpc/API/Rpc/Guild.cs similarity index 50% rename from src/Discord.Net.Rpc/API/Rpc/RpcUserGuild.cs rename to src/Discord.Net.Rpc/API/Rpc/Guild.cs index 4df8e1f7e..1d6bf3678 100644 --- a/src/Discord.Net.Rpc/API/Rpc/RpcUserGuild.cs +++ b/src/Discord.Net.Rpc/API/Rpc/Guild.cs @@ -1,13 +1,18 @@ #pragma warning disable CS1591 using Newtonsoft.Json; +using System.Collections.Generic; namespace Discord.API.Rpc { - public class RpcUserGuild + public class Guild { [JsonProperty("id")] public ulong Id { get; set; } [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("icon_url")] + public string IconUrl { get; set; } + [JsonProperty("members")] + public IEnumerable Members { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GuildMember.cs b/src/Discord.Net.Rpc/API/Rpc/GuildMember.cs new file mode 100644 index 000000000..af74dd919 --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/GuildMember.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class GuildMember + { + [JsonProperty("user")] + public User User { get; set; } + [JsonProperty("status")] + public UserStatus Status { get; set; } + /*[JsonProperty("activity")] + public object Activity { get; set; }*/ + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/GuildCreatedEvent.cs b/src/Discord.Net.Rpc/API/Rpc/GuildSummary.cs similarity index 85% rename from src/Discord.Net.Rpc/API/Rpc/GuildCreatedEvent.cs rename to src/Discord.Net.Rpc/API/Rpc/GuildSummary.cs index 6cfaa862f..c36da5267 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GuildCreatedEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GuildSummary.cs @@ -2,7 +2,7 @@ namespace Discord.API.Rpc { - public class GuildCreatedEvent + public class GuildSummary { [JsonProperty("id")] public ulong Id { get; set; } diff --git a/src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs b/src/Discord.Net.Rpc/API/Rpc/Message.cs similarity index 90% rename from src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs rename to src/Discord.Net.Rpc/API/Rpc/Message.cs index 47c71bab0..a72fba123 100644 --- a/src/Discord.Net.Rpc/API/Rpc/RpcMessage.cs +++ b/src/Discord.Net.Rpc/API/Rpc/Message.cs @@ -2,7 +2,7 @@ namespace Discord.API.Rpc { - public class RpcMessage : Message + public class Message : Discord.API.Message { [JsonProperty("blocked")] public Optional IsBlocked { get; } diff --git a/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs b/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs index 75b35ef56..41ff13288 100644 --- a/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs @@ -7,6 +7,6 @@ namespace Discord.API.Rpc [JsonProperty("channel_id")] public ulong ChannelId { get; set; } [JsonProperty("message")] - public RpcMessage Message { get; set; } + public Message Message { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/RpcChannel.cs b/src/Discord.Net.Rpc/API/Rpc/RpcChannel.cs deleted file mode 100644 index 074672a96..000000000 --- a/src/Discord.Net.Rpc/API/Rpc/RpcChannel.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rpc -{ - public class RpcChannel : Channel - { - [JsonProperty("voice_states")] - public VoiceState[] VoiceStates { get; set; } - } -} diff --git a/src/Discord.Net.Rpc/API/Rpc/RpcGuild.cs b/src/Discord.Net.Rpc/API/Rpc/RpcGuild.cs deleted file mode 100644 index b470de2ba..000000000 --- a/src/Discord.Net.Rpc/API/Rpc/RpcGuild.cs +++ /dev/null @@ -1,13 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API.Rpc -{ - public class RpcGuild : Guild - { - [JsonProperty("online")] - public int Online { get; set; } - [JsonProperty("members")] - public GuildMember[] Members { get; set; } - } -} diff --git a/src/Discord.Net.Rpc/API/Rpc/SelectVoiceChannelParams.cs b/src/Discord.Net.Rpc/API/Rpc/SelectChannelParams.cs similarity index 60% rename from src/Discord.Net.Rpc/API/Rpc/SelectVoiceChannelParams.cs rename to src/Discord.Net.Rpc/API/Rpc/SelectChannelParams.cs index e7f980c32..52c9b00e8 100644 --- a/src/Discord.Net.Rpc/API/Rpc/SelectVoiceChannelParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/SelectChannelParams.cs @@ -3,9 +3,11 @@ using Newtonsoft.Json; namespace Discord.API.Rpc { - public class SelectVoiceChannelParams + public class SelectChannelParams { [JsonProperty("channel_id")] public ulong? ChannelId { get; set; } + [JsonProperty("force")] + public Optional Force { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/UserVoiceSettings.cs b/src/Discord.Net.Rpc/API/Rpc/UserVoiceSettings.cs new file mode 100644 index 000000000..9c876a66f --- /dev/null +++ b/src/Discord.Net.Rpc/API/Rpc/UserVoiceSettings.cs @@ -0,0 +1,18 @@ +#pragma warning disable CS1591 + +using Newtonsoft.Json; + +namespace Discord.API.Rpc +{ + public class UserVoiceSettings + { + [JsonProperty("userId")] + internal ulong UserId { get; set; } + [JsonProperty("pan")] + public Optional Pan { get; set; } + [JsonProperty("volume")] + public Optional Volume { get; set; } + [JsonProperty("mute")] + public Optional Mute { get; set; } + } +} diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs index a966ef149..2a9ae21bf 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.Events.cs @@ -26,20 +26,20 @@ namespace Discord.Rpc private readonly AsyncEvent> _readyEvent = new AsyncEvent>(); //Channel - public event Func ChannelCreated + public event Func ChannelCreated { add { _channelCreatedEvent.Add(value); } remove { _channelCreatedEvent.Remove(value); } } - private readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); //Guild - public event Func GuildCreated + public event Func GuildCreated { add { _guildCreatedEvent.Add(value); } remove { _guildCreatedEvent.Remove(value); } } - private readonly AsyncEvent> _guildCreatedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _guildCreatedEvent = new AsyncEvent>(); public event Func GuildStatusUpdated { add { _guildStatusUpdatedEvent.Add(value); } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 708e18878..fc8aff6a4 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -265,6 +266,59 @@ namespace Discord.Rpc await ApiClient.SendChannelUnsubscribeAsync(GetEventName(events[i]), channelId); } + public async Task GetRpcGuildAsync(ulong id) + { + var model = await ApiClient.SendGetGuildAsync(id).ConfigureAwait(false); + return RpcGuild.Create(this, model); + } + public async Task> GetRpcGuildsAsync() + { + var models = await ApiClient.SendGetGuildsAsync().ConfigureAwait(false); + return models.Guilds.Select(x => RpcGuildSummary.Create(x)).ToImmutableArray(); + } + public async Task GetRpcChannelAsync(ulong id) + { + var model = await ApiClient.SendGetChannelAsync(id).ConfigureAwait(false); + return RpcChannel.Create(this, model); + } + public async Task> GetRpcChannelsAsync(ulong guildId) + { + var models = await ApiClient.SendGetChannelsAsync(guildId).ConfigureAwait(false); + return models.Channels.Select(x => RpcChannelSummary.Create(x)).ToImmutableArray(); + } + + public async Task SelectTextChannelAsync(IChannel channel) + { + var model = await ApiClient.SendSelectTextChannelAsync(channel.Id).ConfigureAwait(false); + return RpcChannel.Create(this, model) as IMessageChannel; + } + public async Task SelectTextChannelAsync(RpcChannelSummary channel) + { + var model = await ApiClient.SendSelectTextChannelAsync(channel.Id).ConfigureAwait(false); + return RpcChannel.Create(this, model) as IMessageChannel; + } + public async Task SelectTextChannelAsync(ulong channelId) + { + var model = await ApiClient.SendSelectTextChannelAsync(channelId).ConfigureAwait(false); + return RpcChannel.Create(this, model) as IMessageChannel; + } + + public async Task SelectVoiceChannelAsync(IChannel channel, bool force = false) + { + var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force).ConfigureAwait(false); + return RpcChannel.Create(this, model) as IRpcAudioChannel; + } + public async Task SelectVoiceChannelAsync(RpcChannelSummary channel, bool force = false) + { + var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force).ConfigureAwait(false); + return RpcChannel.Create(this, model) as IRpcAudioChannel; + } + public async Task SelectVoiceChannelAsync(ulong channelId, bool force = false) + { + var model = await ApiClient.SendSelectVoiceChannelAsync(channelId, force).ConfigureAwait(false); + return RpcChannel.Create(this, model) as IRpcAudioChannel; + } + public async Task GetVoiceSettingsAsync() { var model = await ApiClient.GetVoiceSettingsAsync().ConfigureAwait(false); @@ -279,6 +333,12 @@ namespace Discord.Rpc func(settings); await ApiClient.SetVoiceSettingsAsync(settings).ConfigureAwait(false); } + public async Task SetUserVoiceSettingsAsync(ulong userId, Action func) + { + var settings = new API.Rpc.UserVoiceSettings(); + func(settings); + await ApiClient.SetUserVoiceSettingsAsync(userId, settings).ConfigureAwait(false); + } private static string GetEventName(RpcGlobalEvent rpcEvent) { @@ -363,8 +423,8 @@ namespace Discord.Rpc case "CHANNEL_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); - var data = (payload.Value as JToken).ToObject(_serializer); - var channel = RpcChannel.Create(data); + var data = (payload.Value as JToken).ToObject(_serializer); + var channel = RpcChannelSummary.Create(data); await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); } @@ -374,8 +434,8 @@ namespace Discord.Rpc case "GUILD_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); - var data = (payload.Value as JToken).ToObject(_serializer); - var guild = RpcGuild.Create(data); + var data = (payload.Value as JToken).ToObject(_serializer); + var guild = RpcGuildSummary.Create(data); await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false); } @@ -394,7 +454,7 @@ namespace Discord.Rpc case "VOICE_STATE_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false); - var data = (payload.Value as JToken).ToObject(_serializer); + var data = (payload.Value as JToken).ToObject(_serializer); var voiceState = RpcVoiceState.Create(this, data); await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); @@ -403,7 +463,7 @@ namespace Discord.Rpc case "VOICE_STATE_UPDATE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); - var data = (payload.Value as JToken).ToObject(_serializer); + var data = (payload.Value as JToken).ToObject(_serializer); var voiceState = RpcVoiceState.Create(this, data); await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); @@ -412,7 +472,7 @@ namespace Discord.Rpc case "VOICE_STATE_DELETE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false); - var data = (payload.Value as JToken).ToObject(_serializer); + var data = (payload.Value as JToken).ToObject(_serializer); var voiceState = RpcVoiceState.Create(this, data); await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false); diff --git a/src/Discord.Net.Rpc/Entities/Channels/IRpcAudioChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/IRpcAudioChannel.cs new file mode 100644 index 000000000..4fa01104a --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/IRpcAudioChannel.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Discord.Rpc +{ + public interface IRpcAudioChannel : IAudioChannel + { + IReadOnlyCollection VoiceStates { get; } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Channels/IRpcMessageChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/IRpcMessageChannel.cs new file mode 100644 index 000000000..8e69c1b30 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/IRpcMessageChannel.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Discord.Rpc +{ + public interface IRpcMessageChannel : IMessageChannel + { + IReadOnlyCollection CachedMessages { get; } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Channels/IRpcPrivateChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/IRpcPrivateChannel.cs new file mode 100644 index 000000000..ae43c8675 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/IRpcPrivateChannel.cs @@ -0,0 +1,6 @@ +namespace Discord.Rpc +{ + public interface IRpcPrivateChannel + { + } +} diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs index fc4d6c012..cd69fd38e 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs @@ -1,32 +1,40 @@ -using System.Diagnostics; -using Model = Discord.API.Rpc.ChannelCreatedEvent; +using System; + +using Model = Discord.API.Rpc.Channel; namespace Discord.Rpc { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RpcChannel + public class RpcChannel : RpcEntity { - public ulong Id { get; } - public string Name { get; set; } - public ChannelType Type { get; set; } + public string Name { get; private set; } - internal RpcChannel(ulong id) + internal RpcChannel(DiscordRpcClient discord, ulong id) + : base(discord, id) { - Id = id; } - internal static RpcChannel Create(Model model) + internal static RpcChannel Create(DiscordRpcClient discord, Model model) { - var entity = new RpcChannel(model.Id); - entity.Update(model); - return entity; + if (model.GuildId.IsSpecified) + return RpcGuildChannel.Create(discord, model); + else + return CreatePrivate(discord, model); } - internal void Update(Model model) + internal static RpcChannel CreatePrivate(DiscordRpcClient discord, Model model) { - Name = model.Name; - Type = model.Type; + switch (model.Type) + { + case ChannelType.DM: + return RpcDMChannel.Create(discord, model); + case ChannelType.Group: + return RpcGroupChannel.Create(discord, model); + default: + throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); + } + } + internal virtual void Update(Model model) + { + if (model.Name.IsSpecified) + Name = model.Name.Value; } - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}, {Type})"; } } diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcChannelSummary.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcChannelSummary.cs new file mode 100644 index 000000000..72679ac58 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcChannelSummary.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using Model = Discord.API.Rpc.ChannelSummary; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcChannelSummary + { + public ulong Id { get; } + public string Name { get; set; } + public ChannelType Type { get; set; } + + internal RpcChannelSummary(ulong id) + { + Id = id; + } + internal static RpcChannelSummary Create(Model model) + { + var entity = new RpcChannelSummary(model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Name = model.Name; + Type = model.Type; + } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}, {Type})"; + } +} diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs new file mode 100644 index 000000000..78d38d595 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -0,0 +1,124 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Rpc.Channel; + +namespace Discord.Rpc +{ + public class RpcDMChannel : RpcChannel, IRpcMessageChannel, IRpcPrivateChannel, IDMChannel + { + public IReadOnlyCollection CachedMessages { get; private set; } + + internal RpcDMChannel(DiscordRpcClient discord, ulong id) + : base(discord, id) + { + } + internal static new RpcDMChannel Create(DiscordRpcClient discord, Model model) + { + var entity = new RpcDMChannel(discord, model.Id); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + CachedMessages = model.Messages.Select(x => RpcMessage.Create(Discord, Id, x)).ToImmutableArray(); + } + + public Task CloseAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); + + //TODO: Use RPC cache + public Task GetMessageAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + + public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, 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() => Id.ToString(); + private string DebuggerDisplay => $"({Id}, DM)"; + + //IDMChannel + IUser IDMChannel.Recipient { get { throw new NotSupportedException(); } } + + //IPrivateChannel + IReadOnlyCollection IPrivateChannel.Recipients { get { throw new NotSupportedException(); } } + + //IMessageChannel + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id, options); + else + return null; + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(limit, options); + else + return AsyncEnumerable.Empty>(); + } + 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>(); + } + 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>(); + } + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); + + //IChannel + string IChannel.Name { get { throw new NotSupportedException(); } } + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + { + throw new NotSupportedException(); + } + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs new file mode 100644 index 000000000..79ff1afd3 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -0,0 +1,123 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Rpc.Channel; + +namespace Discord.Rpc +{ + public class RpcGroupChannel : RpcChannel, IRpcMessageChannel, IRpcAudioChannel, IRpcPrivateChannel, IGroupChannel + { + public IReadOnlyCollection CachedMessages { get; private set; } + public IReadOnlyCollection VoiceStates { get; private set; } + + internal RpcGroupChannel(DiscordRpcClient discord, ulong id) + : base(discord, id) + { + } + internal new static RpcGroupChannel Create(DiscordRpcClient discord, Model model) + { + var entity = new RpcGroupChannel(discord, model.Id); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + CachedMessages = model.Messages.Select(x => RpcMessage.Create(Discord, Id, x)).ToImmutableArray(); + VoiceStates = model.VoiceStates.Select(x => RpcVoiceState.Create(Discord, x)).ToImmutableArray(); + } + + public Task LeaveAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); + + //TODO: Use RPC cache + public Task GetMessageAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + + public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, 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() => Id.ToString(); + private string DebuggerDisplay => $"({Id}, Group)"; + + //IPrivateChannel + IReadOnlyCollection IPrivateChannel.Recipients { get { throw new NotSupportedException(); } } + + //IMessageChannel + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id, options); + else + return null; + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(limit, options); + else + return AsyncEnumerable.Empty>(); + } + 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>(); + } + 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>(); + } + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); + + //IChannel + string IChannel.Name { get { throw new NotSupportedException(); } } + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + { + throw new NotSupportedException(); + } + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs new file mode 100644 index 000000000..897244b55 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Discord.API.Rest; +using Model = Discord.API.Rpc.Channel; +using Discord.Rest; + +namespace Discord.Rpc +{ + public class RpcGuildChannel : RpcChannel, IGuildChannel + { + public ulong GuildId { get; } + public int Position { get; private set; } + + internal RpcGuildChannel(DiscordRpcClient discord, ulong id, ulong guildId) + : base(discord, id) + { + GuildId = guildId; + } + internal new static RpcGuildChannel Create(DiscordRpcClient discord, Model model) + { + switch (model.Type) + { + case ChannelType.Text: + return RpcTextChannel.Create(discord, model); + case ChannelType.Voice: + return RpcVoiceChannel.Create(discord, model); + default: + throw new InvalidOperationException("Unknown guild channel type"); + } + } + internal override void Update(Model model) + { + base.Update(model); + if (model.Position.IsSpecified) + Position = model.Position.Value; + } + + 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 Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, RequestOptions options = null) + => ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms, options); + public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, RequestOptions options = null) + => ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms, options); + public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) + => ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options); + public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) + => ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options); + + public async Task> GetInvitesAsync(RequestOptions options = null) + => await ChannelHelper.GetInvitesAsync(this, Discord, options); + public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true, RequestOptions options = null) + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options); + + public override string ToString() => Name; + + //IGuildChannel + async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options); + async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) + => await CreateInviteAsync(maxAge, maxUses, isTemporary, options); + + IReadOnlyCollection IGuildChannel.PermissionOverwrites { get { throw new NotSupportedException(); } } + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) + { + throw new NotSupportedException(); + } + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) + { + throw new NotSupportedException(); + } + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + { + throw new NotSupportedException(); + } + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, 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(); + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs new file mode 100644 index 000000000..45705aa6f --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs @@ -0,0 +1,113 @@ +using Discord.API.Rest; +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Rpc.Channel; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcTextChannel : RpcGuildChannel, IRpcMessageChannel, ITextChannel + { + public IReadOnlyCollection CachedMessages { get; private set; } + + public string Mention => MentionUtils.MentionChannel(Id); + + internal RpcTextChannel(DiscordRpcClient discord, ulong id, ulong guildId) + : base(discord, id, guildId) + { + } + internal new static RpcVoiceChannel Create(DiscordRpcClient discord, Model model) + { + var entity = new RpcVoiceChannel(discord, model.Id, model.GuildId.Value); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + CachedMessages = model.Messages.Select(x => RpcMessage.Create(Discord, Id, x)).ToImmutableArray(); + } + + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); + + //TODO: Use RPC cache + public Task GetMessageAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + + public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, 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 => $"{Name} ({Id}, Text)"; + + //ITextChannel + string ITextChannel.Topic { get { throw new NotSupportedException(); } } + + //IMessageChannel + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id, options); + else + return null; + } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return GetMessagesAsync(limit, options); + else + return AsyncEnumerable.Empty>(); + } + 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>(); + } + 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>(); + } + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, options); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) + => await SendMessageAsync(text, isTTS, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); + } +} diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcVoiceChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcVoiceChannel.cs new file mode 100644 index 000000000..1e6510a38 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcVoiceChannel.cs @@ -0,0 +1,49 @@ +using Discord.API.Rest; +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 Model = Discord.API.Rpc.Channel; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcVoiceChannel : RpcGuildChannel, IRpcAudioChannel, IVoiceChannel + { + public int UserLimit { get; private set; } + public int Bitrate { get; private set; } + public IReadOnlyCollection VoiceStates { get; private set; } + + internal RpcVoiceChannel(DiscordRpcClient discord, ulong id, ulong guildId) + : base(discord, id, guildId) + { + } + internal new static RpcVoiceChannel Create(DiscordRpcClient discord, Model model) + { + var entity = new RpcVoiceChannel(discord, model.Id, model.GuildId.Value); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + if (model.UserLimit.IsSpecified) + UserLimit = model.UserLimit.Value; + if (model.Bitrate.IsSpecified) + Bitrate = model.Bitrate.Value; + VoiceStates = model.VoiceStates.Select(x => RpcVoiceState.Create(Discord, x)).ToImmutableArray(); + } + + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); + + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; + + //IVoiceChannel + Task IVoiceChannel.ConnectAsync() { throw new NotSupportedException(); } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs index c8ee39ffc..7352d9e92 100644 --- a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs +++ b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuild.cs @@ -1,27 +1,33 @@ -using System.Diagnostics; -using Model = Discord.API.Rpc.GuildCreatedEvent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Model = Discord.API.Rpc.Guild; namespace Discord.Rpc { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RpcGuild + public class RpcGuild : RpcEntity { - public ulong Id { get; } - public string Name { get; set; } + public string Name { get; private set; } + public string IconUrl { get; private set; } + public IReadOnlyCollection Users { get; private set; } - internal RpcGuild(ulong id) + internal RpcGuild(DiscordRpcClient discord, ulong id) + : base(discord, id) { - Id = id; } - internal static RpcGuild Create(Model model) + internal static RpcGuild Create(DiscordRpcClient discord, Model model) { - var entity = new RpcGuild(model.Id); + var entity = new RpcGuild(discord, model.Id); entity.Update(model); return entity; } internal void Update(Model model) { Name = model.Name; + IconUrl = model.IconUrl; + Users = model.Members.Select(x => RpcGuildUser.Create(Discord, x)).ToImmutableArray(); } public override string ToString() => Name; diff --git a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs index 4b74249cc..f443d7aa3 100644 --- a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs +++ b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildStatus.cs @@ -6,12 +6,12 @@ namespace Discord.Rpc [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RpcGuildStatus { - public RpcGuild Guild { get; } + public RpcGuildSummary Guild { get; } public int Online { get; private set; } internal RpcGuildStatus(ulong guildId) { - Guild = new RpcGuild(guildId); + Guild = new RpcGuildSummary(guildId); } internal static RpcGuildStatus Create(Model model) { diff --git a/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildSummary.cs b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildSummary.cs new file mode 100644 index 000000000..4f9bff2c9 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Guilds/RpcGuildSummary.cs @@ -0,0 +1,30 @@ +using System.Diagnostics; +using Model = Discord.API.Rpc.GuildSummary; + +namespace Discord.Rpc +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RpcGuildSummary + { + public ulong Id { get; } + public string Name { get; private set; } + + internal RpcGuildSummary(ulong id) + { + Id = id; + } + internal static RpcGuildSummary Create(Model model) + { + var entity = new RpcGuildSummary(model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Name = model.Name; + } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; + } +} diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs index d9aa2b3f0..72c2e134a 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using Model = Discord.API.Rpc.RpcMessage; +using Model = Discord.API.Rpc.Message; namespace Discord.Rpc { diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcSystemMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcSystemMessage.cs index 7cf222dc7..734ef38bc 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcSystemMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcSystemMessage.cs @@ -1,6 +1,6 @@ using Discord.Rest; using System.Diagnostics; -using Model = Discord.API.Rpc.RpcMessage; +using Model = Discord.API.Rpc.Message; namespace Discord.Rpc { diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs index f400701f6..aa1d21973 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Threading.Tasks; -using Model = Discord.API.Rpc.RpcMessage; +using Model = Discord.API.Rpc.Message; namespace Discord.Rpc { diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcGuildUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcGuildUser.cs new file mode 100644 index 000000000..f4ca63750 --- /dev/null +++ b/src/Discord.Net.Rpc/Entities/Users/RpcGuildUser.cs @@ -0,0 +1,29 @@ +using Model = Discord.API.Rpc.GuildMember; + +namespace Discord.Rpc +{ + public class RpcGuildUser : RpcUser + { + private UserStatus _status; + + public override UserStatus Status => _status; + //public object Acitivity { get; private set; } + + internal RpcGuildUser(DiscordRpcClient discord, ulong id) + : base(discord, id) + { + } + internal static RpcGuildUser Create(DiscordRpcClient discord, Model model) + { + var entity = new RpcGuildUser(discord, model.User.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + base.Update(model.User); + _status = model.Status; + //Activity = model.Activity; + } + } +} diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs index 33db9bba1..0ccf16f16 100644 --- a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs +++ b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs @@ -6,7 +6,7 @@ using Model = Discord.API.User; namespace Discord.Rpc { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RpcUser : RpcEntity, IUser, IUpdateable + public class RpcUser : RpcEntity, IUser { public bool IsBot { get; private set; } public string Username { get; private set; } @@ -40,12 +40,6 @@ namespace Discord.Rpc if (model.Username.IsSpecified) Username = model.Username.Value; } - - public virtual async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetUserAsync(Id, options); - Update(model); - } public Task CreateDMChannelAsync(RequestOptions options = null) => UserHelper.CreateDMChannelAsync(this, Discord, options); diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcVoiceState.cs b/src/Discord.Net.Rpc/Entities/Users/RpcVoiceState.cs index ee392f5cd..f18a51434 100644 --- a/src/Discord.Net.Rpc/Entities/Users/RpcVoiceState.cs +++ b/src/Discord.Net.Rpc/Entities/Users/RpcVoiceState.cs @@ -1,6 +1,6 @@ using System; using System.Diagnostics; -using Model = Discord.API.Rpc.VoiceStateEvent; +using Model = Discord.API.Rpc.ExtendedVoiceState; namespace Discord.Rpc { diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 9601d5323..820f3dbcb 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; From 5551a9d52483d7eac4ab12b5a3f96ea3af8cd3be Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 03:15:04 -0300 Subject: [PATCH 075/102] Exposed RequestOptions in DiscordRpcClient --- src/Discord.Net.Rpc/DiscordRpcClient.cs | 92 +++++++++++-------------- 1 file changed, 40 insertions(+), 52 deletions(-) diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index fc8aff6a4..56f32255a 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -221,123 +221,111 @@ namespace Discord.Rpc } } - public async Task AuthorizeAsync(string[] scopes, string rpcToken = null) + public async Task AuthorizeAsync(string[] scopes, string rpcToken = null, RequestOptions options = null) { await ConnectAsync(true).ConfigureAwait(false); - var result = await ApiClient.SendAuthorizeAsync(scopes, rpcToken).ConfigureAwait(false); + var result = await ApiClient.SendAuthorizeAsync(scopes, rpcToken, options).ConfigureAwait(false); await DisconnectAsync().ConfigureAwait(false); return result.Code; } - public async Task SubscribeGlobal(params RpcGlobalEvent[] events) + public async Task SubscribeGlobal(RpcGlobalEvent evnt, RequestOptions options = null) { - Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); - for (int i = 0; i < events.Length; i++) - await ApiClient.SendGlobalSubscribeAsync(GetEventName(events[i])); + await ApiClient.SendGlobalSubscribeAsync(GetEventName(evnt), options); } - public async Task UnsubscribeGlobal(params RpcGlobalEvent[] events) + public async Task UnsubscribeGlobal(RpcGlobalEvent evnt, RequestOptions options = null) { - Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); - for (int i = 0; i < events.Length; i++) - await ApiClient.SendGlobalUnsubscribeAsync(GetEventName(events[i])); + await ApiClient.SendGlobalUnsubscribeAsync(GetEventName(evnt), options); } - public async Task SubscribeGuild(ulong guildId, params RpcChannelEvent[] events) + public async Task SubscribeGuild(ulong guildId, RpcChannelEvent evnt, RequestOptions options = null) { - Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); - for (int i = 0; i < events.Length; i++) - await ApiClient.SendGuildSubscribeAsync(GetEventName(events[i]), guildId); + await ApiClient.SendGuildSubscribeAsync(GetEventName(evnt), guildId, options); } - public async Task UnsubscribeGuild(ulong guildId, params RpcChannelEvent[] events) + public async Task UnsubscribeGuild(ulong guildId, RpcChannelEvent evnt, RequestOptions options = null) { - Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); - for (int i = 0; i < events.Length; i++) - await ApiClient.SendGuildUnsubscribeAsync(GetEventName(events[i]), guildId); + await ApiClient.SendGuildUnsubscribeAsync(GetEventName(evnt), guildId, options); } - public async Task SubscribeChannel(ulong channelId, params RpcChannelEvent[] events) + public async Task SubscribeChannel(ulong channelId, RpcChannelEvent evnt, RequestOptions options = null) { - Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); - for (int i = 0; i < events.Length; i++) - await ApiClient.SendChannelSubscribeAsync(GetEventName(events[i]), channelId); + await ApiClient.SendChannelSubscribeAsync(GetEventName(evnt), channelId); } - public async Task UnsubscribeChannel(ulong channelId, params RpcChannelEvent[] events) + public async Task UnsubscribeChannel(ulong channelId, RpcChannelEvent evnt, RequestOptions options = null) { - Preconditions.AtLeast(events?.Length ?? 0, 1, nameof(events)); - for (int i = 0; i < events.Length; i++) - await ApiClient.SendChannelUnsubscribeAsync(GetEventName(events[i]), channelId); + await ApiClient.SendChannelUnsubscribeAsync(GetEventName(evnt), channelId); } - public async Task GetRpcGuildAsync(ulong id) + public async Task GetRpcGuildAsync(ulong id, RequestOptions options = null) { - var model = await ApiClient.SendGetGuildAsync(id).ConfigureAwait(false); + var model = await ApiClient.SendGetGuildAsync(id, options).ConfigureAwait(false); return RpcGuild.Create(this, model); } - public async Task> GetRpcGuildsAsync() + public async Task> GetRpcGuildsAsync(RequestOptions options = null) { - var models = await ApiClient.SendGetGuildsAsync().ConfigureAwait(false); + var models = await ApiClient.SendGetGuildsAsync(options).ConfigureAwait(false); return models.Guilds.Select(x => RpcGuildSummary.Create(x)).ToImmutableArray(); } - public async Task GetRpcChannelAsync(ulong id) + public async Task GetRpcChannelAsync(ulong id, RequestOptions options = null) { - var model = await ApiClient.SendGetChannelAsync(id).ConfigureAwait(false); + var model = await ApiClient.SendGetChannelAsync(id, options).ConfigureAwait(false); return RpcChannel.Create(this, model); } - public async Task> GetRpcChannelsAsync(ulong guildId) + public async Task> GetRpcChannelsAsync(ulong guildId, RequestOptions options = null) { - var models = await ApiClient.SendGetChannelsAsync(guildId).ConfigureAwait(false); + var models = await ApiClient.SendGetChannelsAsync(guildId, options).ConfigureAwait(false); return models.Channels.Select(x => RpcChannelSummary.Create(x)).ToImmutableArray(); } - public async Task SelectTextChannelAsync(IChannel channel) + public async Task SelectTextChannelAsync(IChannel channel, RequestOptions options = null) { - var model = await ApiClient.SendSelectTextChannelAsync(channel.Id).ConfigureAwait(false); + var model = await ApiClient.SendSelectTextChannelAsync(channel.Id, options).ConfigureAwait(false); return RpcChannel.Create(this, model) as IMessageChannel; } - public async Task SelectTextChannelAsync(RpcChannelSummary channel) + public async Task SelectTextChannelAsync(RpcChannelSummary channel, RequestOptions options = null) { - var model = await ApiClient.SendSelectTextChannelAsync(channel.Id).ConfigureAwait(false); + var model = await ApiClient.SendSelectTextChannelAsync(channel.Id, options).ConfigureAwait(false); return RpcChannel.Create(this, model) as IMessageChannel; } - public async Task SelectTextChannelAsync(ulong channelId) + public async Task SelectTextChannelAsync(ulong channelId, RequestOptions options = null) { - var model = await ApiClient.SendSelectTextChannelAsync(channelId).ConfigureAwait(false); + var model = await ApiClient.SendSelectTextChannelAsync(channelId, options).ConfigureAwait(false); return RpcChannel.Create(this, model) as IMessageChannel; } - public async Task SelectVoiceChannelAsync(IChannel channel, bool force = false) + public async Task SelectVoiceChannelAsync(IChannel channel, bool force = false, RequestOptions options = null) { - var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force).ConfigureAwait(false); + var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force, options).ConfigureAwait(false); return RpcChannel.Create(this, model) as IRpcAudioChannel; } - public async Task SelectVoiceChannelAsync(RpcChannelSummary channel, bool force = false) + public async Task SelectVoiceChannelAsync(RpcChannelSummary channel, bool force = false, RequestOptions options = null) { - var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force).ConfigureAwait(false); + var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force, options).ConfigureAwait(false); return RpcChannel.Create(this, model) as IRpcAudioChannel; } - public async Task SelectVoiceChannelAsync(ulong channelId, bool force = false) + public async Task SelectVoiceChannelAsync(ulong channelId, bool force = false, RequestOptions options = null) { - var model = await ApiClient.SendSelectVoiceChannelAsync(channelId, force).ConfigureAwait(false); + var model = await ApiClient.SendSelectVoiceChannelAsync(channelId, force, options).ConfigureAwait(false); return RpcChannel.Create(this, model) as IRpcAudioChannel; } - public async Task GetVoiceSettingsAsync() + public async Task GetVoiceSettingsAsync(RequestOptions options = null) { - var model = await ApiClient.GetVoiceSettingsAsync().ConfigureAwait(false); + var model = await ApiClient.GetVoiceSettingsAsync(options).ConfigureAwait(false); return VoiceSettings.Create(model); } - public async Task SetVoiceSettingsAsync(Action func) + public async Task SetVoiceSettingsAsync(Action func, RequestOptions options = null) { var settings = new API.Rpc.VoiceSettings(); settings.Input = new VoiceDeviceSettings(); settings.Output = new VoiceDeviceSettings(); settings.Mode = new VoiceMode(); func(settings); - await ApiClient.SetVoiceSettingsAsync(settings).ConfigureAwait(false); + await ApiClient.SetVoiceSettingsAsync(settings, options).ConfigureAwait(false); } - public async Task SetUserVoiceSettingsAsync(ulong userId, Action func) + public async Task SetUserVoiceSettingsAsync(ulong userId, Action func, RequestOptions options = null) { var settings = new API.Rpc.UserVoiceSettings(); func(settings); - await ApiClient.SetUserVoiceSettingsAsync(userId, settings).ConfigureAwait(false); + await ApiClient.SetUserVoiceSettingsAsync(userId, settings, options).ConfigureAwait(false); } private static string GetEventName(RpcGlobalEvent rpcEvent) From bedf601bfe8f017a1337688c2dd4e749ae536ac5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 14:33:39 -0300 Subject: [PATCH 076/102] Added RestGuildUser IsMuted/IsDeafened --- .../Entities/Users/RestGuildUser.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 1ea4e2117..c24d03703 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -16,6 +16,8 @@ namespace Discord.Rest 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 @@ -47,6 +49,8 @@ namespace Discord.Rest _joinedAtTicks = model.JoinedAt.UtcTicks; if (model.Nick.IsSpecified) Nickname = model.Nick.Value; + IsDeafened = model.Deaf; + IsMuted = model.Mute; UpdateRoles(model.Roles); } private void UpdateRoles(ulong[] roleIds) @@ -63,8 +67,16 @@ namespace Discord.Rest var model = await Discord.ApiClient.GetGuildMemberAsync(GuildId, Id, options); Update(model); } - public Task ModifyAsync(Action func, RequestOptions options = null) - => UserHelper.ModifyAsync(this, Discord, func, options); + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var args = await UserHelper.ModifyAsync(this, Discord, func, options); + if (args.Deaf.IsSpecified) + IsDeafened = args.Deaf.Value; + if (args.Mute.IsSpecified) + IsMuted = args.Mute.Value; + if (args.RoleIds.IsSpecified) + UpdateRoles(args.RoleIds.Value); + } public Task KickAsync(RequestOptions options = null) => UserHelper.KickAsync(this, Discord, options); @@ -75,8 +87,6 @@ namespace Discord.Rest } //IVoiceState - bool IVoiceState.IsDeafened => false; - bool IVoiceState.IsMuted => false; bool IVoiceState.IsSelfDeafened => false; bool IVoiceState.IsSelfMuted => false; bool IVoiceState.IsSuppressed => false; From 8f5b5347b4c8ce576730fe01b9ee8e210bf4cc39 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 14:33:51 -0300 Subject: [PATCH 077/102] Update REST models on modify --- src/Discord.Net.Core/Utils/DateTimeUtils.cs | 18 ++++----- .../Entities/Channels/ChannelHelper.cs | 12 +++--- .../Entities/Channels/RestGuildChannel.cs | 7 +++- .../Entities/Channels/RestTextChannel.cs | 8 ++-- .../Entities/Channels/RestVoiceChannel.cs | 7 +++- .../Entities/Guilds/RestGuild.cs | 40 +++++++++++++++---- .../Entities/Messages/MessageHelper.cs | 6 +-- .../Entities/Messages/RestUserMessage.cs | 7 +++- .../Entities/Roles/RestRole.cs | 7 +++- .../Entities/Roles/RoleHelper.cs | 5 ++- .../Entities/Users/RestSelfUser.cs | 3 +- .../Entities/Users/UserHelper.cs | 8 ++-- 12 files changed, 85 insertions(+), 43 deletions(-) diff --git a/src/Discord.Net.Core/Utils/DateTimeUtils.cs b/src/Discord.Net.Core/Utils/DateTimeUtils.cs index b3496520c..aa127fe29 100644 --- a/src/Discord.Net.Core/Utils/DateTimeUtils.cs +++ b/src/Discord.Net.Core/Utils/DateTimeUtils.cs @@ -2,14 +2,14 @@ namespace Discord { - internal static class DateTimeUtils - { - public static DateTimeOffset FromSnowflake(ulong value) - => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); +internal static class DateTimeUtils +{ + public static DateTimeOffset FromSnowflake(ulong value) + => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); - 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; - } + 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.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 93e996747..ea91d3002 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -17,29 +17,29 @@ namespace Discord.Rest { await client.ApiClient.DeleteChannelAsync(channel.Id, options).ConfigureAwait(false); } - public static async Task ModifyAsync(IGuildChannel channel, BaseDiscordClient client, + public static async Task ModifyAsync(IGuildChannel channel, BaseDiscordClient client, Action func, RequestOptions options) { var args = new ModifyGuildChannelParams(); func(args); - await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); + return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); } - public static async Task ModifyAsync(ITextChannel channel, BaseDiscordClient client, + public static async Task ModifyAsync(ITextChannel channel, BaseDiscordClient client, Action func, RequestOptions options) { var args = new ModifyTextChannelParams(); func(args); - await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); + return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); } - public static async Task ModifyAsync(IVoiceChannel channel, BaseDiscordClient client, + public static async Task ModifyAsync(IVoiceChannel channel, BaseDiscordClient client, Action func, RequestOptions options) { var args = new ModifyVoiceChannelParams(); func(args); - await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); + return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); } //Invites diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 08ef88d46..cacc6d238 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -54,8 +54,11 @@ namespace Discord.Rest var model = await Discord.ApiClient.GetChannelAsync(GuildId, Id, options); Update(model); } - public Task ModifyAsync(Action func, RequestOptions options = null) - => ChannelHelper.ModifyAsync(this, Discord, func, options); + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); + Update(model); + } public Task DeleteAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 2cb2a7d19..4bff8f40f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -33,9 +33,11 @@ namespace Discord.Rest Topic = model.Topic.Value; } - - public Task ModifyAsync(Action func, RequestOptions options = null) - => ChannelHelper.ModifyAsync(this, Discord, func, options); + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); + Update(model); + } public Task GetUserAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetUserAsync(this, Guild, Discord, id, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 8abd3c911..9b0e2e21b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -33,8 +33,11 @@ namespace Discord.Rest UserLimit = model.UserLimit.Value; } - public Task ModifyAsync(Action func, RequestOptions options = null) - => ChannelHelper.ModifyAsync(this, Discord, func, options); + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); + Update(model); + } private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 188148eee..4a01a53d2 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -6,6 +6,8 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Guild; +using EmbedModel = Discord.API.GuildEmbed; +using System.Linq; namespace Discord.Rest { @@ -90,6 +92,11 @@ namespace Discord.Rest Available = true; } + internal void Update(EmbedModel model) + { + EmbedChannelId = model.ChannelId; + IsEmbeddable = model.Enabled; + } //General public async Task UpdateAsync(RequestOptions options = null) @@ -97,14 +104,31 @@ namespace Discord.Rest 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 ModifyChannelsAsync(IEnumerable args, RequestOptions options = null) - => GuildHelper.ModifyChannelsAsync(this, Discord, args, options); - public Task ModifyRolesAsync(IEnumerable args, RequestOptions options = null) - => GuildHelper.ModifyRolesAsync(this, Discord, args, options); + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await GuildHelper.ModifyAsync(this, Discord, func, options); + Update(model); + } + public async Task ModifyEmbedAsync(Action func, RequestOptions options = null) + { + var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options); + Update(model); + } + public async Task ModifyChannelsAsync(IEnumerable args, RequestOptions options = null) + { + var arr = args.ToArray(); + await GuildHelper.ModifyChannelsAsync(this, Discord, arr, options); + } + public async Task ModifyRolesAsync(IEnumerable args, RequestOptions options = null) + { + var models = await GuildHelper.ModifyRolesAsync(this, Discord, args, options); + 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); diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index a203b8e49..6f4df548d 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -3,19 +3,19 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text.RegularExpressions; using System.Threading.Tasks; +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) { var args = new ModifyMessageParams(); func(args); - await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, args, options); + return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, args, options); } public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 4e73ad328..c7ef91738 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -111,8 +111,11 @@ namespace Discord.Rest } } - public Task ModifyAsync(Action func, RequestOptions options) - => MessageHelper.ModifyAsync(this, Discord, func, options); + public async Task ModifyAsync(Action func, RequestOptions options) + { + var model = await MessageHelper.ModifyAsync(this, Discord, func, options); + Update(model); + } public Task DeleteAsync(RequestOptions options) => MessageHelper.DeleteAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 2daf66ec8..d7e89ee28 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -40,8 +40,11 @@ namespace Discord.Rest Permissions = new GuildPermissions(model.Permissions); } - public Task ModifyAsync(Action func, RequestOptions options = null) - => RoleHelper.ModifyAsync(this, Discord, func, options); + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await RoleHelper.ModifyAsync(this, Discord, func, options); + Update(model); + } public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs index 8ecc63893..fbd73894a 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -1,6 +1,7 @@ using Discord.API.Rest; using System; using System.Threading.Tasks; +using Model = Discord.API.Role; namespace Discord.Rest { @@ -12,12 +13,12 @@ namespace Discord.Rest { await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id, options).ConfigureAwait(false); } - public static async Task ModifyAsync(IRole role, BaseDiscordClient client, + public static async Task ModifyAsync(IRole role, BaseDiscordClient client, Action func, RequestOptions options) { var args = new ModifyGuildRoleParams(); func(args); - await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, args, options); + return await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, args, options); } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs index 4dabfaa1c..a8150b5e9 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -47,7 +47,8 @@ namespace Discord.Rest { if (Id != Discord.CurrentUser.Id) throw new InvalidOperationException("Unable to modify this object using a different token."); - await UserHelper.ModifyAsync(this, Discord, func, options); + var model = await UserHelper.ModifyAsync(this, Discord, func, options); + Update(model); } Task ISelfUser.ModifyStatusAsync(Action func, RequestOptions options) { throw new NotSupportedException(); } diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index fee3fd876..d86477520 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -1,24 +1,26 @@ using Discord.API.Rest; using System; using System.Threading.Tasks; +using Model = Discord.API.User; 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 ModifyCurrentUserParams(); func(args); - await client.ApiClient.ModifySelfAsync(args, options); + return await client.ApiClient.ModifySelfAsync(args, options); } - 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 ModifyGuildMemberParams(); func(args); await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args, options); + return args; } public static async Task KickAsync(IGuildUser user, BaseDiscordClient client, From ba35f5bed1b3afdfcbf6bc4772afb9653a3a5698 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 14:43:18 -0300 Subject: [PATCH 078/102] Added IRole.IsMentionable --- src/Discord.Net.Core/API/Common/Role.cs | 2 ++ src/Discord.Net.Core/Entities/Roles/IRole.cs | 2 ++ src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 2 ++ src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/Discord.Net.Core/API/Common/Role.cs b/src/Discord.Net.Core/API/Common/Role.cs index 7442d8c75..6a3659489 100644 --- a/src/Discord.Net.Core/API/Common/Role.cs +++ b/src/Discord.Net.Core/API/Common/Role.cs @@ -13,6 +13,8 @@ namespace Discord.API 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] diff --git a/src/Discord.Net.Core/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs index d7de45b88..aa34fb019 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -15,6 +15,8 @@ namespace Discord 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. diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index d7e89ee28..7369b9461 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -13,6 +13,7 @@ namespace Discord.Rest public Color Color { get; private set; } public bool IsHoisted { get; private set; } public bool IsManaged { get; private set; } + public bool IsMentionable { get; private set; } public string Name { get; private set; } public GuildPermissions Permissions { get; private set; } public int Position { get; private set; } @@ -35,6 +36,7 @@ namespace Discord.Rest Name = model.Name; IsHoisted = model.Hoist; IsManaged = model.Managed; + IsMentionable = model.Mentionable; Position = model.Position; Color = new Color(model.Color); Permissions = new GuildPermissions(model.Permissions); diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index b5b83a4f4..7b47709f5 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -15,6 +15,7 @@ namespace Discord.WebSocket public Color Color { get; private set; } public bool IsHoisted { get; private set; } public bool IsManaged { get; private set; } + public bool IsMentionable { get; private set; } public string Name { get; private set; } public GuildPermissions Permissions { get; private set; } public int Position { get; private set; } @@ -38,6 +39,7 @@ namespace Discord.WebSocket Name = model.Name; IsHoisted = model.Hoist; IsManaged = model.Managed; + IsMentionable = model.Mentionable; Position = model.Position; Color = new Color(model.Color); Permissions = new GuildPermissions(model.Permissions); From 3576c4868060edf63233c72aa4db5ccdf1cedd4a Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 14:47:01 -0300 Subject: [PATCH 079/102] Added null check to AsyncEvent --- src/Discord.Net.Core/Utils/AsyncEvent.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Discord.Net.Core/Utils/AsyncEvent.cs b/src/Discord.Net.Core/Utils/AsyncEvent.cs index 0a4d55ed7..a7fdeddf2 100644 --- a/src/Discord.Net.Core/Utils/AsyncEvent.cs +++ b/src/Discord.Net.Core/Utils/AsyncEvent.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; namespace Discord { internal class AsyncEvent + where T : class { private readonly object _subLock = new object(); internal ImmutableArray _subscriptions; @@ -19,11 +20,13 @@ namespace Discord 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)); lock (_subLock) _subscriptions = _subscriptions.Remove(subscriber); } From 01205d911db71a6adc0346bbc39ddcc48040c8a6 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 16:01:47 -0300 Subject: [PATCH 080/102] Fixed null refs when message cache is disabled --- .../Entities/Channels/SocketTextChannel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index c35c6b370..e28e37493 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -88,9 +88,9 @@ namespace Discord.WebSocket => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) - => _messages.Add(msg); + => _messages?.Add(msg); internal SocketMessage RemoveMessage(ulong id) - => _messages.Remove(id); + => _messages?.Remove(id); //Users public override SocketGuildUser GetUser(ulong id) From 005bc8283ad82b1ffadb076557a4bd9617489382 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 16:02:07 -0300 Subject: [PATCH 081/102] Fixed CreateGuildBan ignoring DeleteMessageDays, fixes #256 --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 2 +- src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index fc62d3be0..da3d689d5 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -601,7 +601,7 @@ namespace Discord.API Preconditions.AtLeast(args.DeleteMessageDays, 0, nameof(args.DeleteMessageDays)); options = RequestOptions.CreateOrClone(options); - await SendJsonAsync("PUT", $"guilds/{guildId}/bans/{userId}", args, options: options).ConfigureAwait(false); + await SendAsync("PUT", $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}", options: options).ConfigureAwait(false); } public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) { diff --git a/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs b/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs index 9c049e8a8..331bb3986 100644 --- a/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs @@ -6,7 +6,6 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public class CreateGuildBanParams { - [JsonProperty("delete-message-days")] public Optional DeleteMessageDays { get; set; } } } From 82429fdcd177e0c595f569642d65ce48c28f96eb Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 16:10:47 -0300 Subject: [PATCH 082/102] Removed unneeded attribute --- src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs b/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs index 331bb3986..724112bc0 100644 --- a/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateGuildBanParams.cs @@ -1,9 +1,6 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; - namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public class CreateGuildBanParams { public Optional DeleteMessageDays { get; set; } From 960119d96558599bd2b4b96597fb49d5f6e19372 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 16:18:53 -0300 Subject: [PATCH 083/102] Updated RelationshipType --- src/Discord.Net.Core/API/Common/RelationshipType.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/API/Common/RelationshipType.cs b/src/Discord.Net.Core/API/Common/RelationshipType.cs index 7cd1efd2a..94f0f73b4 100644 --- a/src/Discord.Net.Core/API/Common/RelationshipType.cs +++ b/src/Discord.Net.Core/API/Common/RelationshipType.cs @@ -5,6 +5,7 @@ namespace Discord.API { Friend = 1, Blocked = 2, - Pending = 4 + IncomingPending = 3, + OutgoingPending = 4 } } From 5c33e2875714918349d9cec28d3210dab97abc15 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 16:59:17 -0300 Subject: [PATCH 084/102] Added missing ConfigureAwaits --- src/Discord.Net.Commands/CommandService.cs | 6 ++-- .../API/DiscordRestApiClient.cs | 2 +- .../Net/Queue/RequestQueueBucket.cs | 2 +- .../Net/WebSockets/DefaultWebSocketClient.cs | 4 +-- .../Utils/Paging/PagedEnumerator.cs | 2 +- src/Discord.Net.Rest/DiscordRestClient.cs | 4 +-- .../Entities/Channels/ChannelHelper.cs | 18 +++++----- .../Entities/Channels/RestDMChannel.cs | 12 +++---- .../Entities/Channels/RestGroupChannel.cs | 12 +++---- .../Entities/Channels/RestGuildChannel.cs | 20 +++++------ .../Entities/Channels/RestTextChannel.cs | 14 ++++---- .../Entities/Channels/RestVoiceChannel.cs | 2 +- .../Entities/Guilds/GuildHelper.cs | 6 ++-- .../Entities/Guilds/RestGuild.cs | 34 +++++++++---------- .../Entities/Invites/RestInvite.cs | 2 +- .../Entities/Messages/MessageHelper.cs | 8 ++--- .../Entities/Messages/RestUserMessage.cs | 2 +- .../Entities/Roles/RestRole.cs | 2 +- .../Entities/Roles/RoleHelper.cs | 2 +- .../Entities/Users/RestGuildUser.cs | 4 +-- .../Entities/Users/RestSelfUser.cs | 4 +-- .../Entities/Users/RestUser.cs | 4 +-- .../Entities/Users/UserHelper.cs | 8 ++--- src/Discord.Net.Rest/Utils/TypingNotifier.cs | 4 +-- src/Discord.Net.Rpc/DiscordRpcClient.cs | 14 ++++---- .../Entities/Channels/RpcDMChannel.cs | 12 +++---- .../Entities/Channels/RpcGroupChannel.cs | 12 +++---- .../Entities/Channels/RpcGuildChannel.cs | 8 ++--- .../Entities/Channels/RpcTextChannel.cs | 10 +++--- src/Discord.Net.Rpc/Entities/Users/RpcUser.cs | 2 +- .../API/DiscordVoiceApiClient.cs | 8 ++--- .../Audio/AudioClient.cs | 2 +- .../DiscordSocketClient.cs | 14 ++++---- .../Entities/Channels/SocketDMChannel.cs | 10 +++--- .../Entities/Channels/SocketGroupChannel.cs | 10 +++--- .../Entities/Channels/SocketGuildChannel.cs | 16 ++++----- .../Entities/Channels/SocketTextChannel.cs | 10 +++--- .../Entities/Guilds/SocketGuild.cs | 20 +++++------ .../Entities/Users/SocketSelfUser.cs | 4 +-- .../Entities/Users/SocketUser.cs | 2 +- 40 files changed, 166 insertions(+), 166 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 9b77b65e6..509237310 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -201,7 +201,7 @@ namespace Discord.Commands var commands = searchResult.Commands; for (int i = commands.Count - 1; i >= 0; i--) { - var preconditionResult = await commands[i].CheckPreconditions(context); + var preconditionResult = await commands[i].CheckPreconditions(context).ConfigureAwait(false); if (!preconditionResult.IsSuccess) { if (commands.Count == 1) @@ -210,7 +210,7 @@ namespace Discord.Commands continue; } - var parseResult = await commands[i].Parse(context, searchResult, preconditionResult); + var parseResult = await commands[i].Parse(context, searchResult, preconditionResult).ConfigureAwait(false); if (!parseResult.IsSuccess) { if (parseResult.Error == CommandError.MultipleMatches) @@ -235,7 +235,7 @@ namespace Discord.Commands } } - return await commands[i].Execute(context, parseResult); + return await commands[i].Execute(context, parseResult).ConfigureAwait(false); } return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index da3d689d5..baa0698d8 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -116,7 +116,7 @@ namespace Discord.API _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); if (FetchCurrentUser) - CurrentUser = await GetMyUserAsync(new RequestOptions { IgnoreState = true }); + CurrentUser = await GetMyUserAsync(new RequestOptions { IgnoreState = true }).ConfigureAwait(false); LoginState = LoginState.LoggedIn; } diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs index 00668c315..a94d0f05e 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs @@ -130,7 +130,7 @@ namespace Discord.Net.Queue if (millis <= 0 || !await _semaphore.WaitAsync(millis).ConfigureAwait(false)) throw new TimeoutException(); - if (!await _semaphore.WaitAsync(0)) + if (!await _semaphore.WaitAsync(0).ConfigureAwait(false)) { await _queue.RaiseRateLimitTriggered(Id, this, null).ConfigureAwait(false); diff --git a/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs index 2e4a90ad8..540e6f3b9 100644 --- a/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs +++ b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs @@ -54,7 +54,7 @@ namespace Discord.Net.WebSockets await _sendLock.WaitAsync().ConfigureAwait(false); try { - await ConnectInternalAsync(host); + await ConnectInternalAsync(host).ConfigureAwait(false); } finally { @@ -86,7 +86,7 @@ namespace Discord.Net.WebSockets await _sendLock.WaitAsync().ConfigureAwait(false); try { - await DisconnectInternalAsync(); + await DisconnectInternalAsync().ConfigureAwait(false); } finally { diff --git a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs index e4572e653..730f9517a 100644 --- a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs +++ b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs @@ -45,7 +45,7 @@ namespace Discord if (_info.Remaining == 0) return false; - var data = await _source._getPage(_info, cancelToken); + var data = await _source._getPage(_info, cancelToken).ConfigureAwait(false); Current = new Page(_info, data); _info.Page++; diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 2e9ff861c..f36c0fb06 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -78,14 +78,14 @@ namespace Discord.Rest async Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode) { if (mode == CacheMode.AllowDownload) - return await GetChannelAsync(id); + return await GetChannelAsync(id).ConfigureAwait(false); else return null; } async Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode) { if (mode == CacheMode.AllowDownload) - return await GetPrivateChannelsAsync(); + return await GetPrivateChannelsAsync().ConfigureAwait(false); else return ImmutableArray.Create(); } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index ea91d3002..b8843802a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -23,7 +23,7 @@ namespace Discord.Rest { var args = new ModifyGuildChannelParams(); func(args); - return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); + return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options).ConfigureAwait(false); } public static async Task ModifyAsync(ITextChannel channel, BaseDiscordClient client, Action func, @@ -31,7 +31,7 @@ namespace Discord.Rest { var args = new ModifyTextChannelParams(); func(args); - return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); + return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options).ConfigureAwait(false); } public static async Task ModifyAsync(IVoiceChannel channel, BaseDiscordClient client, Action func, @@ -39,14 +39,14 @@ namespace Discord.Rest { var args = new ModifyVoiceChannelParams(); func(args); - return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options); + return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, args, options).ConfigureAwait(false); } //Invites public static async Task> GetInvitesAsync(IChannel channel, BaseDiscordClient client, RequestOptions options) { - var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options); + var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options).ConfigureAwait(false); return models.Select(x => RestInviteMetadata.Create(client, x)).ToImmutableArray(); } public static async Task CreateInviteAsync(IChannel channel, BaseDiscordClient client, @@ -57,7 +57,7 @@ namespace Discord.Rest args.MaxAge = maxAge.Value; if (maxUses.HasValue) args.MaxUses = maxUses.Value; - var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options); + var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false); return RestInviteMetadata.Create(client, model); } @@ -83,7 +83,7 @@ namespace Discord.Rest }; if (info.Position != null) args.RelativeMessageId = info.Position.Value; - var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options); + var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options).ConfigureAwait(false); return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray(); ; }, nextPage: (info, lastPage) => @@ -164,7 +164,7 @@ namespace Discord.Rest 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); + var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id, options).ConfigureAwait(false); if (model == null) return null; var user = RestGuildUser.Create(client, guild, model); @@ -186,7 +186,7 @@ namespace Discord.Rest }; if (info.Position != null) args.AfterUserId = info.Position.Value; - var models = await guild.Discord.ApiClient.GetGuildMembersAsync(guild.Id, args, options); + var models = await guild.Discord.ApiClient.GetGuildMembersAsync(guild.Id, args, options).ConfigureAwait(false); return models .Select(x => RestGuildUser.Create(client, guild, x)) .Where(x => x.GetPermissions(channel).ReadMessages) @@ -207,7 +207,7 @@ namespace Discord.Rest public static async Task TriggerTypingAsync(IMessageChannel channel, BaseDiscordClient client, RequestOptions options = null) { - await client.ApiClient.TriggerTypingIndicatorAsync(channel.Id, options); + await client.ApiClient.TriggerTypingIndicatorAsync(channel.Id, options).ConfigureAwait(false); } public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 355bd656c..fa3dae82b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -36,7 +36,7 @@ namespace Discord.Rest public override async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetChannelAsync(Id, options); + var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); Update(model); } public Task CloseAsync(RequestOptions options = null) @@ -94,7 +94,7 @@ namespace Discord.Rest async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return null; } @@ -120,14 +120,14 @@ namespace Discord.Rest return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options); + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 9aeb99447..0868568cd 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -51,7 +51,7 @@ namespace Discord.Rest public override async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetChannelAsync(Id, options); + var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); Update(model); } public Task LeaveAsync(RequestOptions options = null) @@ -104,7 +104,7 @@ namespace Discord.Rest async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return null; } @@ -130,14 +130,14 @@ namespace Discord.Rest return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options); + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index cacc6d238..63e2ec509 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -51,12 +51,12 @@ namespace Discord.Rest public override async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetChannelAsync(GuildId, Id, options); + 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); + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } public Task DeleteAsync(RequestOptions options = null) @@ -118,30 +118,30 @@ namespace Discord.Rest } public async Task> GetInvitesAsync(RequestOptions options = null) - => await ChannelHelper.GetInvitesAsync(this, Discord, options); + => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true, RequestOptions options = null) - => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options); + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options).ConfigureAwait(false); public override string ToString() => Name; //IGuildChannel async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options); + => await GetInvitesAsync(options).ConfigureAwait(false); async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) - => await CreateInviteAsync(maxAge, maxUses, isTemporary, options); + => await CreateInviteAsync(maxAge, maxUses, isTemporary, 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); + => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) - => await AddPermissionOverwriteAsync(user, permissions, options); + => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) - => await RemovePermissionOverwriteAsync(role, options); + => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) - => await RemovePermissionOverwriteAsync(user, options); + => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overriden //Overriden in Text/Voice //TODO: Does this actually override? diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 4bff8f40f..d8fc6637a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -35,7 +35,7 @@ namespace Discord.Rest public async Task ModifyAsync(Action func, RequestOptions options = null) { - var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } @@ -76,7 +76,7 @@ namespace Discord.Rest async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetUserAsync(id, options); + return await GetUserAsync(id, options).ConfigureAwait(false); else return null; } @@ -92,7 +92,7 @@ namespace Discord.Rest async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return null; } @@ -118,14 +118,14 @@ namespace Discord.Rest return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options); + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 9b0e2e21b..a19a5cb38 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -35,7 +35,7 @@ namespace Discord.Rest public async Task ModifyAsync(Action func, RequestOptions options = null) { - var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 966914960..a27475e3c 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -62,7 +62,7 @@ namespace Discord.Rest public static async Task> GetBansAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { - var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options); + var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); } @@ -70,12 +70,12 @@ namespace Discord.Rest ulong userId, int pruneDays, RequestOptions options) { var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays }; - await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args, options); + await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args, options).ConfigureAwait(false); } public static async Task RemoveBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options) { - await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId, options); + await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false); } //Channels diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 4a01a53d2..7b68260de 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -100,18 +100,18 @@ namespace Discord.Rest //General public async Task UpdateAsync(RequestOptions options = null) - => Update(await Discord.ApiClient.GetGuildAsync(Id, options)); + => 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); + 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); + var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } public async Task ModifyChannelsAsync(IEnumerable args, RequestOptions options = null) @@ -121,7 +121,7 @@ namespace Discord.Rest } public async Task ModifyRolesAsync(IEnumerable args, RequestOptions options = null) { - var models = await GuildHelper.ModifyRolesAsync(this, Discord, args, options); + var models = await GuildHelper.ModifyRolesAsync(this, Discord, args, options).ConfigureAwait(false); foreach (var model in models) { var role = GetRole(model.Id); @@ -179,7 +179,7 @@ namespace Discord.Rest 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); + var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options).ConfigureAwait(false); _roles = _roles.Add(role.Id, role); return role; } @@ -205,58 +205,58 @@ namespace Discord.Rest IReadOnlyCollection IGuild.Roles => Roles; async Task> IGuild.GetBansAsync(RequestOptions options) - => await GetBansAsync(options); + => await GetBansAsync(options).ConfigureAwait(false); async Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetChannelsAsync(options); + 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); + return await GetChannelAsync(id, options).ConfigureAwait(false); else return null; } async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options) - => await CreateTextChannelAsync(name, options); + => await CreateTextChannelAsync(name, options).ConfigureAwait(false); async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) - => await CreateVoiceChannelAsync(name, options); + => await CreateVoiceChannelAsync(name, options).ConfigureAwait(false); async Task> IGuild.GetIntegrationsAsync(RequestOptions options) - => await GetIntegrationsAsync(options); + => await GetIntegrationsAsync(options).ConfigureAwait(false); async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) - => await CreateIntegrationAsync(id, type, options); + => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); async Task> IGuild.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options); + => await GetInvitesAsync(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); + => 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); + 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); + return await GetCurrentUserAsync(options).ConfigureAwait(false); else return null; } async Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return (await GetUsersAsync(options).Flatten()).ToImmutableArray(); + return (await GetUsersAsync(options).Flatten().ConfigureAwait(false)).ToImmutableArray(); else return ImmutableArray.Create(); } diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 4c07ea9ac..4c870f3f4 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -35,7 +35,7 @@ namespace Discord.Rest public async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetInviteAsync(Code, options); + var model = await Discord.ApiClient.GetInviteAsync(Code, options).ConfigureAwait(false); Update(model); } public Task DeleteAsync(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 6f4df548d..358f6f5a9 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -15,23 +15,23 @@ namespace Discord.Rest { var args = new ModifyMessageParams(); func(args); - return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, args, options); + return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, args, options).ConfigureAwait(false); } public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.DeleteMessageAsync(msg.Channel.Id, msg.Id, options); + await client.ApiClient.DeleteMessageAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); } public static async Task PinAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.AddPinAsync(msg.Channel.Id, msg.Id, options); + await client.ApiClient.AddPinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); } public static async Task UnpinAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options); + await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); } public static ImmutableArray ParseTags(string text, IMessageChannel channel, IGuild guild, ImmutableArray userMentions) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index c7ef91738..21f87c18f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -113,7 +113,7 @@ namespace Discord.Rest public async Task ModifyAsync(Action func, RequestOptions options) { - var model = await MessageHelper.ModifyAsync(this, Discord, func, options); + var model = await MessageHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } public Task DeleteAsync(RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 7369b9461..16b521b1d 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -44,7 +44,7 @@ namespace Discord.Rest public async Task ModifyAsync(Action func, RequestOptions options = null) { - var model = await RoleHelper.ModifyAsync(this, Discord, func, options); + var model = await RoleHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } public Task DeleteAsync(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs index fbd73894a..0b102098c 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -18,7 +18,7 @@ namespace Discord.Rest { var args = new ModifyGuildRoleParams(); func(args); - return await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, args, options); + return await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, args, options).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index c24d03703..6d1cac024 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -64,12 +64,12 @@ namespace Discord.Rest public override async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetGuildMemberAsync(GuildId, Id, options); + 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); + var args = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); if (args.Deaf.IsSpecified) IsDeafened = args.Deaf.Value; if (args.Mute.IsSpecified) diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs index a8150b5e9..368d8c798 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -37,7 +37,7 @@ namespace Discord.Rest public override async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetMyUserAsync(options); + var model = await Discord.ApiClient.GetMyUserAsync(options).ConfigureAwait(false); if (model.Id != Id) throw new InvalidOperationException("Unable to update this object using a different token."); Update(model); @@ -47,7 +47,7 @@ namespace Discord.Rest { 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); + 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 5e085b821..681be24c4 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -42,7 +42,7 @@ namespace Discord.Rest public virtual async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetUserAsync(Id, options); + var model = await Discord.ApiClient.GetUserAsync(Id, options).ConfigureAwait(false); Update(model); } @@ -56,6 +56,6 @@ namespace Discord.Rest Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(null); async Task IUser.CreateDMChannelAsync(RequestOptions options) - => await CreateDMChannelAsync(options); + => await CreateDMChannelAsync(options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index d86477520..545703f0d 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -12,28 +12,28 @@ namespace Discord.Rest { var args = new ModifyCurrentUserParams(); func(args); - return await client.ApiClient.ModifySelfAsync(args, options); + return await client.ApiClient.ModifySelfAsync(args, options).ConfigureAwait(false); } public static async Task ModifyAsync(IGuildUser user, BaseDiscordClient client, Action func, RequestOptions options) { var args = new ModifyGuildMemberParams(); func(args); - await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args, options); + await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, args, options).ConfigureAwait(false); return args; } public static async Task KickAsync(IGuildUser user, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, options); + await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, 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)); + return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args, options).ConfigureAwait(false)); } } } diff --git a/src/Discord.Net.Rest/Utils/TypingNotifier.cs b/src/Discord.Net.Rest/Utils/TypingNotifier.cs index 433553e00..45b715a76 100644 --- a/src/Discord.Net.Rest/Utils/TypingNotifier.cs +++ b/src/Discord.Net.Rest/Utils/TypingNotifier.cs @@ -29,10 +29,10 @@ namespace Discord.Rest { try { - await _channel.TriggerTypingAsync(_options); + await _channel.TriggerTypingAsync(_options).ConfigureAwait(false); } catch { } - await Task.Delay(9750, token); + await Task.Delay(9750, token).ConfigureAwait(false); } } catch (OperationCanceledException) { } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 56f32255a..d0443d15e 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -103,7 +103,7 @@ namespace Discord.Rpc //Abort connection on timeout var _ = Task.Run(async () => { - await Task.Delay(ConnectionTimeout); + await Task.Delay(ConnectionTimeout).ConfigureAwait(false); connectTask.TrySetException(new TimeoutException()); }); @@ -231,27 +231,27 @@ namespace Discord.Rpc public async Task SubscribeGlobal(RpcGlobalEvent evnt, RequestOptions options = null) { - await ApiClient.SendGlobalSubscribeAsync(GetEventName(evnt), options); + await ApiClient.SendGlobalSubscribeAsync(GetEventName(evnt), options).ConfigureAwait(false); } public async Task UnsubscribeGlobal(RpcGlobalEvent evnt, RequestOptions options = null) { - await ApiClient.SendGlobalUnsubscribeAsync(GetEventName(evnt), options); + await ApiClient.SendGlobalUnsubscribeAsync(GetEventName(evnt), options).ConfigureAwait(false); } public async Task SubscribeGuild(ulong guildId, RpcChannelEvent evnt, RequestOptions options = null) { - await ApiClient.SendGuildSubscribeAsync(GetEventName(evnt), guildId, options); + await ApiClient.SendGuildSubscribeAsync(GetEventName(evnt), guildId, options).ConfigureAwait(false); } public async Task UnsubscribeGuild(ulong guildId, RpcChannelEvent evnt, RequestOptions options = null) { - await ApiClient.SendGuildUnsubscribeAsync(GetEventName(evnt), guildId, options); + await ApiClient.SendGuildUnsubscribeAsync(GetEventName(evnt), guildId, options).ConfigureAwait(false); } public async Task SubscribeChannel(ulong channelId, RpcChannelEvent evnt, RequestOptions options = null) { - await ApiClient.SendChannelSubscribeAsync(GetEventName(evnt), channelId); + await ApiClient.SendChannelSubscribeAsync(GetEventName(evnt), channelId).ConfigureAwait(false); } public async Task UnsubscribeChannel(ulong channelId, RpcChannelEvent evnt, RequestOptions options = null) { - await ApiClient.SendChannelUnsubscribeAsync(GetEventName(evnt), channelId); + await ApiClient.SendChannelUnsubscribeAsync(GetEventName(evnt), channelId).ConfigureAwait(false); } public async Task GetRpcGuildAsync(ulong id, RequestOptions options = null) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index 78d38d595..c7c5a8438 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -72,14 +72,14 @@ namespace Discord.Rpc async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return null; } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit, options); + return GetMessagesAsync(limit, options).ConfigureAwait(false); else return AsyncEnumerable.Empty>(); } @@ -98,14 +98,14 @@ namespace Discord.Rpc return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options); + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index 79ff1afd3..5062350fa 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -71,14 +71,14 @@ namespace Discord.Rpc async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return null; } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit, options); + return GetMessagesAsync(limit, options).ConfigureAwait(false); else return AsyncEnumerable.Empty>(); } @@ -97,14 +97,14 @@ namespace Discord.Rpc return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options); + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs index 897244b55..9b010b903 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs @@ -51,17 +51,17 @@ namespace Discord.Rpc => ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options); public async Task> GetInvitesAsync(RequestOptions options = null) - => await ChannelHelper.GetInvitesAsync(this, Discord, options); + => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true, RequestOptions options = null) - => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options); + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options).ConfigureAwait(false); public override string ToString() => Name; //IGuildChannel async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options); + => await GetInvitesAsync(options).ConfigureAwait(false); async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) - => await CreateInviteAsync(maxAge, maxUses, isTemporary, options); + => await CreateInviteAsync(maxAge, maxUses, isTemporary, options).ConfigureAwait(false); IReadOnlyCollection IGuildChannel.PermissionOverwrites { get { throw new NotSupportedException(); } } OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs index 45705aa6f..b29d392f5 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs @@ -73,7 +73,7 @@ namespace Discord.Rpc async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return null; } @@ -99,14 +99,14 @@ namespace Discord.Rpc return AsyncEnumerable.Empty>(); } async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options); + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); } diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs index 0ccf16f16..be2562114 100644 --- a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs +++ b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs @@ -51,6 +51,6 @@ namespace Discord.Rpc Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(null); async Task IUser.CreateDMChannelAsync(RequestOptions options) - => await CreateDMChannelAsync(options); + => await CreateDMChannelAsync(options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs index d7957b57b..378acd22e 100644 --- a/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordVoiceApiClient.cs @@ -107,7 +107,7 @@ namespace Discord.Audio if (payload != null) bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); await _webSocketClient.SendAsync(bytes, 0, bytes.Length, true).ConfigureAwait(false); - await _sentGatewayMessageEvent.InvokeAsync(opCode); + await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); } public async Task SendAsync(byte[] data, int bytes) { @@ -132,7 +132,7 @@ namespace Discord.Audio UserId = userId, SessionId = sessionId, Token = token - }); + }).ConfigureAwait(false); } public async Task SendSelectProtocol(string externalIp, int externalPort) { @@ -145,7 +145,7 @@ namespace Discord.Audio Port = externalPort, Mode = Mode } - }); + }).ConfigureAwait(false); } public async Task SendSetSpeaking(bool value) { @@ -153,7 +153,7 @@ namespace Discord.Audio { IsSpeaking = value, Delay = 0 - }); + }).ConfigureAwait(false); } public async Task ConnectAsync(string url) diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index de20fcdff..04eec4541 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -269,7 +269,7 @@ namespace Discord.Audio catch { return; } await _audioLogger.DebugAsync("Received Discovery").ConfigureAwait(false); - await ApiClient.SendSelectProtocol(ip, port); + await ApiClient.SendSelectProtocol(ip, port).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 6c59c4b0b..73e183da7 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -163,7 +163,7 @@ namespace Discord.WebSocket //Abort connection on timeout var _ = Task.Run(async () => { - await Task.Delay(ConnectionTimeout); + await Task.Delay(ConnectionTimeout).ConfigureAwait(false); connectTask.TrySetException(new TimeoutException()); }); @@ -410,7 +410,7 @@ namespace Discord.WebSocket //Wait for unsynced guilds to sync first. var unsyncedGuilds = guilds.Select(x => x.SyncPromise).Where(x => !x.IsCompleted).ToImmutableArray(); if (unsyncedGuilds.Length > 0) - await Task.WhenAll(unsyncedGuilds); + await Task.WhenAll(unsyncedGuilds).ConfigureAwait(false); //Download offline members const short batchSize = 50; @@ -1468,10 +1468,10 @@ namespace Discord.WebSocket //Ignored (User only) case "CHANNEL_PINS_ACK": - await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)"); + await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); break; case "CHANNEL_PINS_UPDATE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_UPDATE)"); + 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); @@ -1621,17 +1621,17 @@ namespace Discord.WebSocket => Task.FromResult>(PrivateChannels); async Task> IDiscordClient.GetConnectionsAsync() - => await GetConnectionsAsync(); + => await GetConnectionsAsync().ConfigureAwait(false); async Task IDiscordClient.GetInviteAsync(string inviteId) - => await GetInviteAsync(inviteId); + => await GetInviteAsync(inviteId).ConfigureAwait(false); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode) => Task.FromResult(GetGuild(id)); Task> IDiscordClient.GetGuildsAsync(CacheMode mode) => Task.FromResult>(Guilds); async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon) - => await CreateGuildAsync(name, region, jpegIcon); + => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode) => Task.FromResult(GetUser(id)); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 5e481d3f2..46c6aca85 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -48,7 +48,7 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options).ConfigureAwait(false); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) @@ -118,7 +118,7 @@ namespace Discord.WebSocket async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return GetCachedMessage(id); } @@ -131,11 +131,11 @@ namespace Discord.WebSocket async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 00cf00082..4fa366704 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -71,7 +71,7 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options).ConfigureAwait(false); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) @@ -181,7 +181,7 @@ namespace Discord.WebSocket async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return GetCachedMessage(id); } @@ -194,11 +194,11 @@ namespace Discord.WebSocket async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 637f7c8ff..64ebc5603 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -112,9 +112,9 @@ namespace Discord.WebSocket } public async Task> GetInvitesAsync(RequestOptions options = null) - => await ChannelHelper.GetInvitesAsync(this, Discord, options); + => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true, RequestOptions options = null) - => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options); + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options);.ConfigureAwait(false) public new abstract SocketGuildUser GetUser(ulong id); @@ -129,22 +129,22 @@ namespace Discord.WebSocket ulong IGuildChannel.GuildId => Guild.Id; async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options); + => await GetInvitesAsync(options).ConfigureAwait(false); async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) - => await CreateInviteAsync(maxAge, maxUses, isTemporary, options); + => await CreateInviteAsync(maxAge, maxUses, isTemporary, 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); + => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) - => await AddPermissionOverwriteAsync(user, permissions, options); + => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) - => await RemovePermissionOverwriteAsync(role, options); + => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) - => await RemovePermissionOverwriteAsync(user, options); + => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index e28e37493..dddecbad5 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -54,7 +54,7 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, Guild, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, Guild, options).ConfigureAwait(false); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) @@ -119,7 +119,7 @@ namespace Discord.WebSocket async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options); + return await GetMessageAsync(id, options).ConfigureAwait(false); else return GetCachedMessage(id); } @@ -132,11 +132,11 @@ namespace Discord.WebSocket async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, options); + => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, options); + => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 8d81a961d..54a4d5153 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -387,7 +387,7 @@ namespace Discord.WebSocket public async Task DownloadUsersAsync() { - await Discord.DownloadUsersAsync(new[] { this }); + await Discord.DownloadUsersAsync(new[] { this }).ConfigureAwait(false); } internal void CompleteDownloadUsers() { @@ -495,12 +495,12 @@ namespace Discord.WebSocket } catch (OperationCanceledException) { - await DisconnectAudioAsync(); + await DisconnectAudioAsync().ConfigureAwait(false); } catch (Exception e) { await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false); - await DisconnectAudioAsync(); + await DisconnectAudioAsync().ConfigureAwait(false); } finally { @@ -532,29 +532,29 @@ namespace Discord.WebSocket IReadOnlyCollection IGuild.Roles => Roles; async Task> IGuild.GetBansAsync(RequestOptions options) - => await GetBansAsync(options); + => await GetBansAsync(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)); async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options) - => await CreateTextChannelAsync(name, options); + => await CreateTextChannelAsync(name, options).ConfigureAwait(false); async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) - => await CreateVoiceChannelAsync(name, options); + => await CreateVoiceChannelAsync(name, options).ConfigureAwait(false); async Task> IGuild.GetIntegrationsAsync(RequestOptions options) - => await GetIntegrationsAsync(options); + => await GetIntegrationsAsync(options).ConfigureAwait(false); async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) - => await CreateIntegrationAsync(id, type, options); + => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); async Task> IGuild.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options); + => await GetInvitesAsync(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); + => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Users); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index 926dca5cc..6e230c317 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -80,7 +80,7 @@ namespace Discord.WebSocket Presence = new SocketPresence(status, game); - await SendStatus(status, game); + await SendStatus(status, game).ConfigureAwait(false); } internal async Task SendStatus(UserStatus status, GameEntity? game) { @@ -95,7 +95,7 @@ namespace Discord.WebSocket status, status == UserStatus.AFK, _statusSince != null ? _statusSince.Value.ToUnixTimeMilliseconds() : (long?)null, - gameModel); + gameModel).ConfigureAwait(false); } internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 76af76022..14018b9e2 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -51,6 +51,6 @@ namespace Discord.WebSocket Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(GlobalUser.DMChannel); async Task IUser.CreateDMChannelAsync(RequestOptions options) - => await CreateDMChannelAsync(options); + => await CreateDMChannelAsync(options).ConfigureAwait(false); } } From 49070f3075cd6169e2ee30f6c3e89703d50d0173 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 17:00:20 -0300 Subject: [PATCH 085/102] Went a tad overboard --- src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs | 2 +- src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs | 2 +- .../Entities/Channels/SocketGuildChannel.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index c7c5a8438..fc67e3084 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -79,7 +79,7 @@ namespace Discord.Rpc IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit, options).ConfigureAwait(false); + return GetMessagesAsync(limit, options); else return AsyncEnumerable.Empty>(); } diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index 5062350fa..deffc7e14 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -78,7 +78,7 @@ namespace Discord.Rpc IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit, options).ConfigureAwait(false); + return GetMessagesAsync(limit, options); else return AsyncEnumerable.Empty>(); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 64ebc5603..36a372735 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -114,7 +114,7 @@ namespace Discord.WebSocket public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = true, RequestOptions options = null) - => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options);.ConfigureAwait(false) + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, options).ConfigureAwait(false); public new abstract SocketGuildUser GetUser(ulong id); From db26c0df62be42ba95c8689f1e63342fbf69c0f5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 17:40:15 -0300 Subject: [PATCH 086/102] Removed debug lines --- .../Net/WebSockets/DefaultWebSocketClient.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs index 540e6f3b9..707f7663b 100644 --- a/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs +++ b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs @@ -154,7 +154,6 @@ namespace Discord.Net.WebSockets while (!cancelToken.IsCancellationRequested) { WebSocketReceiveResult socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); - System.Diagnostics.Debug.WriteLine("Got " + socketResult.Count); byte[] result; int resultCount; @@ -193,8 +192,7 @@ namespace Discord.Net.WebSockets resultCount = socketResult.Count; result = buffer.Array; } - - System.Diagnostics.Debug.WriteLine("Start"); + if (socketResult.MessageType == WebSocketMessageType.Text) { string text = Encoding.UTF8.GetString(result, 0, resultCount); @@ -202,7 +200,6 @@ namespace Discord.Net.WebSockets } else await BinaryMessage(result, 0, resultCount).ConfigureAwait(false); - System.Diagnostics.Debug.WriteLine("Stop"); } } catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) From 09495e6d5878387540700f1d1c3b364c2951b44b Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 18:25:40 -0300 Subject: [PATCH 087/102] Made converters internal --- src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs | 2 +- src/Discord.Net.Core/Net/Converters/ImageConverter.cs | 2 +- src/Discord.Net.Core/Net/Converters/NullableUInt64Converter.cs | 2 +- src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs | 2 +- src/Discord.Net.Core/Net/Converters/OptionalConverter.cs | 2 +- .../Net/Converters/PermissionTargetConverter.cs | 2 +- src/Discord.Net.Core/Net/Converters/StringEntityConverter.cs | 2 +- src/Discord.Net.Core/Net/Converters/UInt64ArrayConverter.cs | 2 +- src/Discord.Net.Core/Net/Converters/UInt64Converter.cs | 2 +- src/Discord.Net.Core/Net/Converters/UInt64EntityConverter.cs | 2 +- src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs index 0fb676f1c..0a61806b6 100644 --- a/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Core/Net/Converters/DiscordContractResolver.cs @@ -8,7 +8,7 @@ using System.Reflection; namespace Discord.Net.Converters { - public class DiscordContractResolver : DefaultContractResolver + internal class DiscordContractResolver : DefaultContractResolver { private static readonly TypeInfo _ienumerable = typeof(IEnumerable).GetTypeInfo(); private static readonly MethodInfo _shouldSerialize = typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize"); diff --git a/src/Discord.Net.Core/Net/Converters/ImageConverter.cs b/src/Discord.Net.Core/Net/Converters/ImageConverter.cs index 88e205688..79e8c984d 100644 --- a/src/Discord.Net.Core/Net/Converters/ImageConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/ImageConverter.cs @@ -4,7 +4,7 @@ using System; namespace Discord.Net.Converters { - public class ImageConverter : JsonConverter + internal class ImageConverter : JsonConverter { public static readonly ImageConverter Instance = new ImageConverter(); diff --git a/src/Discord.Net.Core/Net/Converters/NullableUInt64Converter.cs b/src/Discord.Net.Core/Net/Converters/NullableUInt64Converter.cs index 050ac7c32..a2e409292 100644 --- a/src/Discord.Net.Core/Net/Converters/NullableUInt64Converter.cs +++ b/src/Discord.Net.Core/Net/Converters/NullableUInt64Converter.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Discord.Net.Converters { - public class NullableUInt64Converter : JsonConverter + internal class NullableUInt64Converter : JsonConverter { public static readonly NullableUInt64Converter Instance = new NullableUInt64Converter(); diff --git a/src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs b/src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs index a83edb60e..229c8cd87 100644 --- a/src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/ObjectOrIdConverter.cs @@ -4,7 +4,7 @@ using System; namespace Discord.Net.Converters { - public class ObjectOrIdConverter : JsonConverter + internal class ObjectOrIdConverter : JsonConverter { internal static ObjectOrIdConverter Instance; diff --git a/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs b/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs index 37bce7b28..c6965ec61 100644 --- a/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/OptionalConverter.cs @@ -3,7 +3,7 @@ using System; namespace Discord.Net.Converters { - public class OptionalConverter : JsonConverter + internal class OptionalConverter : JsonConverter { internal static OptionalConverter Instance; diff --git a/src/Discord.Net.Core/Net/Converters/PermissionTargetConverter.cs b/src/Discord.Net.Core/Net/Converters/PermissionTargetConverter.cs index 6dc74932f..0ed566a84 100644 --- a/src/Discord.Net.Core/Net/Converters/PermissionTargetConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/PermissionTargetConverter.cs @@ -3,7 +3,7 @@ using System; namespace Discord.Net.Converters { - public class PermissionTargetConverter : JsonConverter + internal class PermissionTargetConverter : JsonConverter { public static readonly PermissionTargetConverter Instance = new PermissionTargetConverter(); diff --git a/src/Discord.Net.Core/Net/Converters/StringEntityConverter.cs b/src/Discord.Net.Core/Net/Converters/StringEntityConverter.cs index 902fb1a75..d7dd58d71 100644 --- a/src/Discord.Net.Core/Net/Converters/StringEntityConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/StringEntityConverter.cs @@ -3,7 +3,7 @@ using System; namespace Discord.Net.Converters { - public class StringEntityConverter : JsonConverter + internal class StringEntityConverter : JsonConverter { public static readonly StringEntityConverter Instance = new StringEntityConverter(); diff --git a/src/Discord.Net.Core/Net/Converters/UInt64ArrayConverter.cs b/src/Discord.Net.Core/Net/Converters/UInt64ArrayConverter.cs index d0a8d170b..a5c2d2096 100644 --- a/src/Discord.Net.Core/Net/Converters/UInt64ArrayConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/UInt64ArrayConverter.cs @@ -5,7 +5,7 @@ using System.Globalization; namespace Discord.Net.Converters { - public class UInt64ArrayConverter : JsonConverter + internal class UInt64ArrayConverter : JsonConverter { public static readonly UInt64ArrayConverter Instance = new UInt64ArrayConverter(); diff --git a/src/Discord.Net.Core/Net/Converters/UInt64Converter.cs b/src/Discord.Net.Core/Net/Converters/UInt64Converter.cs index 6cbcd81f6..27cbe9290 100644 --- a/src/Discord.Net.Core/Net/Converters/UInt64Converter.cs +++ b/src/Discord.Net.Core/Net/Converters/UInt64Converter.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Discord.Net.Converters { - public class UInt64Converter : JsonConverter + internal class UInt64Converter : JsonConverter { public static readonly UInt64Converter Instance = new UInt64Converter(); diff --git a/src/Discord.Net.Core/Net/Converters/UInt64EntityConverter.cs b/src/Discord.Net.Core/Net/Converters/UInt64EntityConverter.cs index 8a102ab22..b8d8f1057 100644 --- a/src/Discord.Net.Core/Net/Converters/UInt64EntityConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/UInt64EntityConverter.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Discord.Net.Converters { - public class UInt64EntityConverter : JsonConverter + internal class UInt64EntityConverter : JsonConverter { public static readonly UInt64EntityConverter Instance = new UInt64EntityConverter(); diff --git a/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs b/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs index cbcc9eab2..c0a287c16 100644 --- a/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs +++ b/src/Discord.Net.Core/Net/Converters/UserStatusConverter.cs @@ -3,7 +3,7 @@ using System; namespace Discord.Net.Converters { - public class UserStatusConverter : JsonConverter + internal class UserStatusConverter : JsonConverter { public static readonly UserStatusConverter Instance = new UserStatusConverter(); From 8b24b01718ca21a18fd861c9b7aaa165d284e044 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 18:26:02 -0300 Subject: [PATCH 088/102] Fixed SO in RestChannel.Create --- .../Entities/Channels/RestChannel.cs | 3 +- .../Entities/Channels/RestTextChannel.cs | 48 ++++++++++++------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 342dd6898..e2a98e282 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -17,9 +17,8 @@ namespace Discord.Rest switch (model.Type) { case ChannelType.Text: - return RestTextChannel.Create(discord, model); case ChannelType.Voice: - return RestVoiceChannel.Create(discord, model); + return RestGuildChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model); case ChannelType.DM: case ChannelType.Group: return CreatePrivate(discord, model) as RestChannel; diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index d8fc6637a..35ba91f02 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -72,22 +72,6 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Name} ({Id}, Text)"; - //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; - } - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return GetUsersAsync(options); - else - return AsyncEnumerable.Empty>(); //Overriden - } - //IMessageChannel async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -128,5 +112,37 @@ namespace Discord.Rest => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); + + //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; + } + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return GetUsersAsync(options); + else + return AsyncEnumerable.Empty>(); + } + + //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; + } + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return GetUsersAsync(options); + else + return AsyncEnumerable.Empty>(); + } } } From c1effbc9713c3761e99393ada9aed03f7c5fc1fe Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 18:32:45 -0300 Subject: [PATCH 089/102] Merged PlatformHelper into ConcurrentHashset --- .../Utils/ConcurrentHashSet.cs | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs index 846ad6348..3a7b39643 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -7,13 +7,32 @@ using System.Threading; namespace Discord { - //Based on https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs + //Based on https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Threading.Tasks.Parallel/src/System/Threading/PlatformHelper.cs //Copyright (c) .NET Foundation and Contributors public static class ConcurrentHashSet { - public static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount; + private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; + private static volatile int s_processorCount; + private static volatile int s_lastProcessorCountRefreshTicks; + + public static int DefaultConcurrencyLevel + { + get + { + int now = Environment.TickCount; + if (s_processorCount == 0 || (now - s_lastProcessorCountRefreshTicks) >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS) + { + s_processorCount = Environment.ProcessorCount; + s_lastProcessorCountRefreshTicks = now; + } + + return s_processorCount; + } + } } + //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}")] internal class ConcurrentHashSet : IReadOnlyCollection { @@ -57,7 +76,7 @@ namespace Discord bucketNo = (hashcode & 0x7fffffff) % bucketCount; lockNo = bucketNo % lockCount; } - private static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount; + private static int DefaultConcurrencyLevel => ConcurrentHashSet.DefaultConcurrencyLevel; private volatile Tables _tables; private readonly IEqualityComparer _comparer; @@ -453,28 +472,4 @@ namespace Discord Monitor.Exit(_tables._locks[i]); } } - - //https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Threading.Tasks.Parallel/src/System/Threading/PlatformHelper.cs - //Copyright (c) .NET Foundation and Contributors - internal static class PlatformHelper - { - private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; - private static volatile int s_processorCount; - private static volatile int s_lastProcessorCountRefreshTicks; - - internal static int ProcessorCount - { - get - { - int now = Environment.TickCount; - if (s_processorCount == 0 || (now - s_lastProcessorCountRefreshTicks) >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS) - { - s_processorCount = Environment.ProcessorCount; - s_lastProcessorCountRefreshTicks = now; - } - - return s_processorCount; - } - } - } } \ No newline at end of file From 21c38412ad1ea80e849d7379af9003f94786bce0 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 18:38:41 -0300 Subject: [PATCH 090/102] Changed snowflake GetCreatedAt() -> CreatedAt --- src/Discord.Net.Core/Entities/ISnowflakeEntity.cs | 3 +++ .../Extensions/SnowflakeEntityExtensions.cs | 10 ---------- src/Discord.Net.Rest/Entities/Channels/RestChannel.cs | 2 ++ .../Entities/Channels/RestVirtualMessageChannel.cs | 4 ++-- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 1 + src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs | 4 +++- src/Discord.Net.Rest/Entities/Messages/RestMessage.cs | 1 + src/Discord.Net.Rest/Entities/RestApplication.cs | 1 + src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 1 + src/Discord.Net.Rest/Entities/Users/RestUser.cs | 4 +++- src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs | 2 ++ src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs | 1 + src/Discord.Net.Rpc/Entities/Users/RpcUser.cs | 2 ++ .../Entities/Channels/SocketChannel.cs | 1 + .../Entities/Guilds/SocketGuild.cs | 1 + .../Entities/Messages/SocketMessage.cs | 1 + .../Entities/Messages/SocketUserMessage.cs | 2 +- src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs | 1 + src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs | 2 ++ 19 files changed, 29 insertions(+), 15 deletions(-) delete mode 100644 src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs diff --git a/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs b/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs index 6eb947bf8..5b099b5ac 100644 --- a/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs +++ b/src/Discord.Net.Core/Entities/ISnowflakeEntity.cs @@ -1,6 +1,9 @@ +using System; + namespace Discord { public interface ISnowflakeEntity : IEntity { + DateTimeOffset CreatedAt { get; } } } diff --git a/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs b/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs deleted file mode 100644 index 271ad9e00..000000000 --- a/src/Discord.Net.Core/Extensions/SnowflakeEntityExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Discord.Extensions -{ - public static class SnowflakeEntityExtensions - { - //TODO: C#7 Candidate for extension property. - public static DateTimeOffset GetCreatedAt(this ISnowflakeEntity entity) => DateTimeUtils.FromSnowflake(entity.Id); - } -} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index e2a98e282..0481d37ed 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -8,6 +8,8 @@ namespace Discord.Rest { public abstract class RestChannel : RestEntity, IChannel, IUpdateable { + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); + internal RestChannel(BaseDiscordClient discord, ulong id) : base(discord, id) { diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs index 75f6e836f..d9fcfb358 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs @@ -1,5 +1,4 @@ -using Discord.Rest; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -12,6 +11,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] internal class RestVirtualMessageChannel : RestEntity, IMessageChannel { + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public string Mention => MentionUtils.MentionChannel(Id); internal RestVirtualMessageChannel(BaseDiscordClient discord, ulong id) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 7b68260de..a0ba7a6de 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -33,6 +33,7 @@ namespace Discord.Rest public string SplashId { get; private set; } internal bool Available { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public ulong DefaultChannelId => Id; public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index c6334bfb1..69c2c9362 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.UserGuild; @@ -13,6 +14,7 @@ namespace Discord.Rest public bool IsOwner { get; private set; } public GuildPermissions Permissions { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); internal RestUserGuild(BaseDiscordClient discord, ulong id) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index ff54dad98..e89d6faf7 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -17,6 +17,7 @@ namespace Discord.Rest public string Content { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public virtual bool IsTTS => false; public virtual bool IsPinned => false; public virtual DateTimeOffset? EditedTimestamp => null; diff --git a/src/Discord.Net.Rest/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index f06f03485..62b434044 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -17,6 +17,7 @@ namespace Discord.Rest public IUser Owner { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public string IconUrl => API.CDN.GetApplicationIconUrl(Id, _iconId); internal RestApplication(BaseDiscordClient discord, ulong id) diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 16b521b1d..6e81ce9df 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -18,6 +18,7 @@ namespace Discord.Rest public GuildPermissions Permissions { get; private set; } public int Position { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public bool IsEveryone => Id == Guild.Id; public string Mention => MentionUtils.MentionRole(Id); diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 681be24c4..25419932f 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; @@ -13,6 +14,7 @@ namespace Discord.Rest public string AvatarId { get; private set; } public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public string Discriminator => DiscriminatorValue.ToString("D4"); public string Mention => MentionUtils.MentionUser(Id); public virtual Game? Game => null; diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs index cd69fd38e..934dae94b 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs @@ -8,6 +8,8 @@ namespace Discord.Rpc { public string Name { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); + internal RpcChannel(DiscordRpcClient discord, ulong id) : base(discord, id) { diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs index 72c2e134a..3f4d102bf 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs @@ -15,6 +15,7 @@ namespace Discord.Rpc public string Content { get; private set; } public Color AuthorColor { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public virtual bool IsTTS => false; public virtual bool IsPinned => false; public virtual bool IsBlocked => false; diff --git a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs index be2562114..360039beb 100644 --- a/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs +++ b/src/Discord.Net.Rpc/Entities/Users/RpcUser.cs @@ -1,4 +1,5 @@ using Discord.Rest; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; @@ -14,6 +15,7 @@ namespace Discord.Rpc public string AvatarId { get; private set; } public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public string Discriminator => DiscriminatorValue.ToString("D4"); public string Mention => MentionUtils.MentionUser(Id); public virtual Game? Game => null; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 820f3dbcb..f982e66b5 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -10,6 +10,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class SocketChannel : SocketEntity, IChannel { + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public IReadOnlyCollection Users => GetUsersInternal(); internal SocketChannel(DiscordSocketClient discord, ulong id) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 54a4d5153..d87b82c6f 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -51,6 +51,7 @@ namespace Discord.WebSocket public string IconId { get; private set; } public string SplashId { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public ulong DefaultChannelId => Id; public string IconUrl => API.CDN.GetGuildIconUrl(Id, IconId); public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, SplashId); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 709f36fa0..0043ff8d2 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -15,6 +15,7 @@ namespace Discord.WebSocket public string Content { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public virtual bool IsTTS => false; public virtual bool IsPinned => false; public virtual DateTimeOffset? EditedTimestamp => null; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 91d798f5b..81a9ff4c7 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -18,7 +18,7 @@ namespace Discord.WebSocket private ImmutableArray _attachments; private ImmutableArray _embeds; private ImmutableArray _tags; - + public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; public override ulong? WebhookId => _webhookId; diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index 7b47709f5..515389da1 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -20,6 +20,7 @@ namespace Discord.WebSocket public GuildPermissions Permissions { get; private set; } public int Position { get; private set; } + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public bool IsEveryone => Id == Guild.Id; public string Mention => MentionUtils.MentionRole(Id); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 14018b9e2..674239be7 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -1,4 +1,5 @@ using Discord.Rest; +using System; using System.Threading.Tasks; using Model = Discord.API.User; using PresenceModel = Discord.API.Presence; @@ -15,6 +16,7 @@ namespace Discord.WebSocket internal abstract SocketPresence Presence { get; set; } public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, AvatarId); + public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); public string Discriminator => DiscriminatorValue.ToString("D4"); public string Mention => MentionUtils.MentionUser(Id); public Game? Game => Presence.Game; From 8ab675e7753b6325cb26a12be19790eae1faa0b8 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 18:41:55 -0300 Subject: [PATCH 091/102] Restrict typereaders to using the cache --- .../Readers/ChannelTypeReader.cs | 6 +++--- src/Discord.Net.Commands/Readers/UserTypeReader.cs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index 8ace89961..e05c02abb 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -14,16 +14,16 @@ namespace Discord.Commands if (context.Guild != null) { var results = new Dictionary(); - var channels = await context.Guild.GetChannelsAsync().ConfigureAwait(false); + var channels = await context.Guild.GetChannelsAsync(CacheMode.CacheOnly).ConfigureAwait(false); ulong id; //By Mention (1.0) if (MentionUtils.TryParseChannel(input, out id)) - AddResult(results, await context.Guild.GetChannelAsync(id).ConfigureAwait(false) as T, 1.00f); + 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).ConfigureAwait(false) as T, 0.90f); + 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))) diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index c77ece4a1..31bdd0b58 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -13,29 +13,29 @@ namespace Discord.Commands public override async Task Read(CommandContext context, string input) { var results = new Dictionary(); - IReadOnlyCollection channelUsers = (await context.Channel.GetUsersAsync().Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way? + IReadOnlyCollection channelUsers = (await context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way? IReadOnlyCollection guildUsers = null; ulong id; if (context.Guild != null) - guildUsers = await context.Guild.GetUsersAsync().ConfigureAwait(false); + guildUsers = await context.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false); //By Mention (1.0) if (MentionUtils.TryParseUser(input, out id)) { if (context.Guild != null) - AddResult(results, await context.Guild.GetUserAsync(id).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).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).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).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) From 6bdc0c09cafb9f542db2745f85c56a36b1471f4e Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 18:44:59 -0300 Subject: [PATCH 092/102] Cleaned up TODOs --- src/Discord.Net.Core/IDiscordClient.cs | 2 -- src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs | 2 -- src/Discord.Net.Core/Net/Rest/IRestClient.cs | 1 - src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 4 +++- .../Entities/Channels/RestGuildChannel.cs | 8 ++++---- .../Entities/Channels/RestVirtualMessageChannel.cs | 1 - .../Entities/Channels/SocketGuildChannel.cs | 4 ++-- 7 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index 92dc6b7ba..4131b1796 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -6,8 +6,6 @@ using System.Threading.Tasks; namespace Discord { - //TODO: Add docstrings - //TODO: Docstrings should explain when REST requests are sent and how many public interface IDiscordClient : IDisposable { ConnectionState ConnectionState { get; } diff --git a/src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs index cacc58bb7..492b3a77d 100644 --- a/src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs @@ -4,8 +4,6 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - //TODO: Allow user-supplied canceltoken - //TODO: Allow specifying timeout via DiscordApiClient public interface IQueuedRequest { CancellationToken CancelToken { get; } diff --git a/src/Discord.Net.Core/Net/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs index 7f90060b6..aa53bea5b 100644 --- a/src/Discord.Net.Core/Net/Rest/IRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/IRestClient.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; namespace Discord.Net.Rest { - //TODO: Add docstrings public interface IRestClient { void SetHeader(string key, string value); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index b8843802a..ccd24e5dd 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -71,7 +71,9 @@ namespace Discord.Rest public static IAsyncEnumerable> GetMessagesAsync(IChannel channel, BaseDiscordClient client, ulong? fromMessageId, Direction dir, int limit, IGuild guild, RequestOptions options) { - //TODO: Test this with Around direction + if (dir == Direction.Around) + throw new NotImplementedException(); //TODO: Impl + return new PagedAsyncEnumerable( DiscordConfig.MaxMessagesPerBatch, async (info, ct) => diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 63e2ec509..2607a2d96 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -144,14 +144,14 @@ namespace Discord.Rest => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overriden //Overriden in Text/Voice //TODO: Does this actually override? + => AsyncEnumerable.Empty>(); //Overriden //Overriden in Text/Voice Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? + => Task.FromResult(null); //Overriden in Text/Voice //IChannel IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overriden in Text/Voice //TODO: Does this actually override? + => AsyncEnumerable.Empty>(); //Overriden in Text/Voice Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overriden in Text/Voice //TODO: Does this actually override? + => Task.FromResult(null); //Overriden in Text/Voice } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs index d9fcfb358..97a0a93e6 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; namespace Discord.Rest { - //TODO: Review this class [DebuggerDisplay(@"{DebuggerDisplay,nq}")] internal class RestVirtualMessageChannel : RestEntity, IMessageChannel { diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 36a372735..4253b3c51 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -153,8 +153,8 @@ namespace Discord.WebSocket //IChannel IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overriden in Text/Voice //TODO: Does this actually override? + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overriden in Text/Voice Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); //Overriden in Text/Voice //TODO: Does this actually override? + => Task.FromResult(GetUser(id)); //Overriden in Text/Voice } } From 7326aac4681dc366ab8cec757516d4e62765cbd3 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 18:45:29 -0300 Subject: [PATCH 093/102] Bumped up concurrent collections concurrency level --- src/Discord.Net.WebSocket/ClientState.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index 13beb0ffb..a452113e2 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -7,7 +7,6 @@ namespace Discord.WebSocket { internal class ClientState { - private const int CollectionConcurrencyLevel = 1; //WebSocket updater/event handler. //TODO: Needs profiling, increase to 2? private const double AverageChannelsPerGuild = 10.22; //Source: Googie2149 private const double AverageUsersPerGuild = 47.78; //Source: Googie2149 private const double CollectionMultiplier = 1.05; //Add 5% buffer to handle growth @@ -33,11 +32,11 @@ namespace Discord.WebSocket { double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount; double estimatedUsersCount = guildCount * AverageUsersPerGuild; - _channels = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(estimatedChannelCount * CollectionMultiplier)); - _dmChannels = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); - _guilds = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); - _users = new ConcurrentDictionary(CollectionConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); - _groupChannels = new ConcurrentHashSet(CollectionConcurrencyLevel, (int)(10 * CollectionMultiplier)); + _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 SocketChannel GetChannel(ulong id) From 3930e1147c79a6932e791f4cd2ee6c6c7d6190c8 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 8 Oct 2016 21:53:49 -0300 Subject: [PATCH 094/102] doc typo --- src/Discord.Net.Core/Format.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index 010d1b0dc..0039836d8 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -14,7 +14,7 @@ /// Returns a markdown-formatted string with strikethrough formatting. public static string Strikethrough(string text) => $"~~{text}~~"; - /// Returns a markdown-formatted string with strikeout formatting. + /// Returns a markdown-formatted string with codeblock formatting. public static string Code(string text, string language = null) { if (language != null || text.Contains("\n")) From 635819b89f5207b50497ecd3202f40ec8dd1ee03 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 10 Oct 2016 20:26:01 -0300 Subject: [PATCH 095/102] Reduced command module lifetime to a single command execution. Removed ModuleAttribute. --- .../Attributes/DontAutoLoadAttribute.cs | 9 ++ .../Attributes/InAttribute.cs | 8 -- .../Attributes/ModuleAttribute.cs | 22 ---- .../Attributes/PreconditionAttribute.cs | 2 +- .../Preconditions/RequireContextAttribute.cs | 2 +- .../RequirePermissionAttribute.cs | 2 +- .../{Command.cs => CommandInfo.cs} | 46 +++---- src/Discord.Net.Commands/CommandParser.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 122 +++++++++--------- src/Discord.Net.Commands/Map/CommandMap.cs | 8 +- .../Map/CommandMapNode.cs | 10 +- src/Discord.Net.Commands/ModuleBase.cs | 15 +++ .../{Module.cs => ModuleInfo.cs} | 41 +++--- src/Discord.Net.Commands/ReflectionUtils.cs | 24 ++-- .../Results/SearchResult.cs | 6 +- .../Utils/ConcurrentHashSet.cs | 2 +- 16 files changed, 161 insertions(+), 160 deletions(-) create mode 100644 src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs delete mode 100644 src/Discord.Net.Commands/Attributes/InAttribute.cs delete mode 100644 src/Discord.Net.Commands/Attributes/ModuleAttribute.cs rename src/Discord.Net.Commands/{Command.cs => CommandInfo.cs} (87%) create mode 100644 src/Discord.Net.Commands/ModuleBase.cs rename src/Discord.Net.Commands/{Module.cs => ModuleInfo.cs} (62%) diff --git a/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs b/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs new file mode 100644 index 000000000..d6a1c646e --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Class)] + public class DontAutoLoadAttribute : Attribute + { + } +} diff --git a/src/Discord.Net.Commands/Attributes/InAttribute.cs b/src/Discord.Net.Commands/Attributes/InAttribute.cs deleted file mode 100644 index 0d4fd9eb0..000000000 --- a/src/Discord.Net.Commands/Attributes/InAttribute.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Discord.Commands -{ - public class InAttribute : Attribute - { - } -} diff --git a/src/Discord.Net.Commands/Attributes/ModuleAttribute.cs b/src/Discord.Net.Commands/Attributes/ModuleAttribute.cs deleted file mode 100644 index 297cce84c..000000000 --- a/src/Discord.Net.Commands/Attributes/ModuleAttribute.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Discord.Commands -{ - [AttributeUsage(AttributeTargets.Class)] - public class ModuleAttribute : Attribute - { - public string Prefix { get; } - public bool AutoLoad { get; set; } - - public ModuleAttribute() - { - Prefix = null; - AutoLoad = true; - } - public ModuleAttribute(string prefix) - { - Prefix = prefix; - AutoLoad = true; - } - } -} diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 90e82607e..067c8e93b 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -6,6 +6,6 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class PreconditionAttribute : Attribute { - public abstract Task CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance); + public abstract Task CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index c14dbd5bf..1cd32e72e 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -21,7 +21,7 @@ namespace Discord.Commands Contexts = contexts; } - public override Task CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance) + public override Task CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) { bool isValid = false; diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs index 3583836d8..26aeac5ec 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs @@ -20,7 +20,7 @@ namespace Discord.Commands GuildPermission = null; } - public override Task CheckPermissions(CommandContext context, Command executingCommand, object moduleInstance) + public override Task CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) { var guildUser = context.User as IGuildUser; diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/CommandInfo.cs similarity index 87% rename from src/Discord.Net.Commands/Command.cs rename to src/Discord.Net.Commands/CommandInfo.cs index b1ec3f0e1..e63fb6d3f 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/CommandInfo.cs @@ -10,16 +10,15 @@ using System.Threading.Tasks; namespace Discord.Commands { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class Command + public class CommandInfo { - private static readonly MethodInfo _convertParamsMethod = typeof(Command).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); + private static readonly MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); - - private readonly object _instance; - private readonly Func, Task> _action; + + private readonly Func _action; public MethodInfo Source { get; } - public Module Module { get; } + public ModuleInfo Module { get; } public string Name { get; } public string Summary { get; } public string Remarks { get; } @@ -30,13 +29,12 @@ namespace Discord.Commands public IReadOnlyList Parameters { get; } public IReadOnlyList Preconditions { get; } - internal Command(MethodInfo source, Module module, object instance, CommandAttribute attribute, string groupPrefix) + internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix) { try { Source = source; Module = module; - _instance = instance; Name = source.Name; @@ -85,18 +83,18 @@ namespace Discord.Commands } } - public async Task CheckPreconditions(CommandContext context) + public async Task CheckPreconditions(CommandContext context, IDependencyMap map = null) { foreach (PreconditionAttribute precondition in Module.Preconditions) { - var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false); + var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); if (!result.IsSuccess) return result; } foreach (PreconditionAttribute precondition in Preconditions) { - var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false); + var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); if (!result.IsSuccess) return result; } @@ -169,11 +167,9 @@ namespace Discord.Commands private IReadOnlyList BuildParameters(MethodInfo methodInfo) { var parameters = methodInfo.GetParameters(); - if (parameters.Length == 0 || parameters[0].ParameterType != typeof(CommandContext)) - throw new InvalidOperationException($"The first parameter of a command must be {nameof(CommandContext)}."); - var paramBuilder = ImmutableArray.CreateBuilder(parameters.Length - 1); - for (int i = 1; i < parameters.Length; i++) + var paramBuilder = ImmutableArray.CreateBuilder(parameters.Length); + for (int i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; var type = parameter.ParameterType; @@ -209,19 +205,23 @@ namespace Discord.Commands } return paramBuilder.ToImmutable(); } - private Func, Task> BuildAction(MethodInfo methodInfo) + private Func BuildAction(MethodInfo methodInfo) { if (methodInfo.ReturnType != typeof(Task)) throw new InvalidOperationException("Commands must return a non-generic Task."); - return (msg, args) => + return (context, args) => { - object[] newArgs = new object[args.Count + 1]; - newArgs[0] = msg; - for (int i = 0; i < args.Count; i++) - newArgs[i + 1] = args[i]; - var result = methodInfo.Invoke(_instance, newArgs); - return result as Task ?? Task.CompletedTask; + var instance = Module.CreateInstance(); + instance.Context = context; + try + { + return methodInfo.Invoke(instance, args) as Task ?? Task.CompletedTask; + } + finally + { + (instance as IDisposable)?.Dispose(); + } }; } diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index b87e67aa0..1808b705d 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -13,7 +13,7 @@ namespace Discord.Commands QuotedParameter } - public static async Task ParseArgs(Command command, CommandContext context, string input, int startPos) + public static async Task ParseArgs(CommandInfo command, CommandContext context, string input, int startPos) { CommandParameter curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 509237310..a76c7bdc3 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -11,18 +11,20 @@ namespace Discord.Commands { public class CommandService { + private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo(); + private readonly SemaphoreSlim _moduleLock; - private readonly ConcurrentDictionary _modules; + private readonly ConcurrentDictionary _moduleDefs; private readonly ConcurrentDictionary _typeReaders; private readonly CommandMap _map; - public IEnumerable Modules => _modules.Select(x => x.Value); - public IEnumerable Commands => _modules.SelectMany(x => x.Value.Commands); + public IEnumerable Modules => _moduleDefs.Select(x => x.Value); + public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Value.Commands); public CommandService() { _moduleLock = new SemaphoreSlim(1, 1); - _modules = new ConcurrentDictionary(); + _moduleDefs = new ConcurrentDictionary(); _map = new CommandMap(); _typeReaders = new ConcurrentDictionary { @@ -45,7 +47,6 @@ namespace Discord.Commands [typeof(IMessage)] = new MessageTypeReader(), [typeof(IUserMessage)] = new MessageTypeReader(), - //[typeof(ISystemMessage)] = new MessageTypeReader(), [typeof(IChannel)] = new ChannelTypeReader(), [typeof(IDMChannel)] = new ChannelTypeReader(), [typeof(IGroupChannel)] = new ChannelTypeReader(), @@ -55,120 +56,99 @@ namespace Discord.Commands [typeof(ITextChannel)] = new ChannelTypeReader(), [typeof(IVoiceChannel)] = new ChannelTypeReader(), - //[typeof(IGuild)] = new GuildTypeReader(), - [typeof(IRole)] = new RoleTypeReader(), - //[typeof(IInvite)] = new InviteTypeReader(), - //[typeof(IInviteMetadata)] = new InviteTypeReader(), - [typeof(IUser)] = new UserTypeReader(), [typeof(IGroupUser)] = new UserTypeReader(), [typeof(IGuildUser)] = new UserTypeReader(), }; } - public void AddTypeReader(TypeReader reader) - { - _typeReaders[typeof(T)] = reader; - } - public void AddTypeReader(Type type, TypeReader reader) - { - _typeReaders[type] = reader; - } - internal TypeReader GetTypeReader(Type type) - { - TypeReader reader; - if (_typeReaders.TryGetValue(type, out reader)) - return reader; - return null; - } - - public async Task Load(object moduleInstance) + //Modules + public async Task AddModule(IDependencyMap dependencyMap = null) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { - if (_modules.ContainsKey(moduleInstance.GetType())) - throw new ArgumentException($"This module has already been loaded."); + if (_moduleDefs.ContainsKey(typeof(T))) + throw new ArgumentException($"This module has already been added."); - var typeInfo = moduleInstance.GetType().GetTypeInfo(); - var moduleAttr = typeInfo.GetCustomAttribute(); - if (moduleAttr == null) - throw new ArgumentException($"Modules must be marked with ModuleAttribute."); + var typeInfo = typeof(T).GetTypeInfo(); + if (!_moduleTypeInfo.IsAssignableFrom(typeInfo)) + throw new ArgumentException($"Modules must inherit ModuleBase."); - return LoadInternal(moduleInstance, moduleAttr, typeInfo, null); + return AddModuleInternal(typeInfo, dependencyMap); } finally { _moduleLock.Release(); } } - private Module LoadInternal(object moduleInstance, ModuleAttribute moduleAttr, TypeInfo typeInfo, IDependencyMap dependencyMap) - { - if (_modules.ContainsKey(moduleInstance.GetType())) - return _modules[moduleInstance.GetType()]; - - var loadedModule = new Module(typeInfo, this, moduleInstance, moduleAttr, dependencyMap); - _modules[moduleInstance.GetType()] = loadedModule; - - foreach (var cmd in loadedModule.Commands) - _map.AddCommand(cmd); - - return loadedModule; - } - public async Task> LoadAssembly(Assembly assembly, IDependencyMap dependencyMap = null) + public async Task> AddModules(Assembly assembly, IDependencyMap dependencyMap = null) { - var modules = ImmutableArray.CreateBuilder(); + var moduleDefs = ImmutableArray.CreateBuilder(); await _moduleLock.WaitAsync().ConfigureAwait(false); try { foreach (var type in assembly.ExportedTypes) { - var typeInfo = type.GetTypeInfo(); - var moduleAttr = typeInfo.GetCustomAttribute(); - if (moduleAttr != null && moduleAttr.AutoLoad) + if (!_moduleDefs.ContainsKey(type)) { - var moduleInstance = ReflectionUtils.CreateObject(typeInfo, this, dependencyMap); - modules.Add(LoadInternal(moduleInstance, moduleAttr, typeInfo, dependencyMap)); + var typeInfo = type.GetTypeInfo(); + if (_moduleTypeInfo.IsAssignableFrom(typeInfo)) + { + var dontAutoLoad = typeInfo.GetCustomAttribute(); + if (dontAutoLoad == null) + moduleDefs.Add(AddModuleInternal(typeInfo, dependencyMap)); + } } } - return modules.ToImmutable(); + return moduleDefs.ToImmutable(); } finally { _moduleLock.Release(); } } + private ModuleInfo AddModuleInternal(TypeInfo typeInfo, IDependencyMap dependencyMap) + { + var moduleDef = new ModuleInfo(typeInfo, this, dependencyMap); + _moduleDefs[typeInfo.BaseType] = moduleDef; + + foreach (var cmd in moduleDef.Commands) + _map.AddCommand(cmd); + + return moduleDef; + } - public async Task Unload(Module module) + public async Task RemoveModule(ModuleInfo module) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { - return UnloadInternal(module.Instance); + return RemoveModuleInternal(module.Source.BaseType); } finally { _moduleLock.Release(); } } - public async Task Unload(object moduleInstance) + public async Task RemoveModule() { await _moduleLock.WaitAsync().ConfigureAwait(false); try { - return UnloadInternal(moduleInstance); + return RemoveModuleInternal(typeof(T)); } finally { _moduleLock.Release(); } } - private bool UnloadInternal(object module) + private bool RemoveModuleInternal(Type type) { - Module unloadedModule; - if (_modules.TryRemove(module.GetType(), out unloadedModule)) + ModuleInfo unloadedModule; + if (_moduleDefs.TryRemove(type, out unloadedModule)) { foreach (var cmd in unloadedModule.Commands) _map.RemoveCommand(cmd); @@ -178,6 +158,24 @@ namespace Discord.Commands return false; } + //Type Readers + public void AddTypeReader(TypeReader reader) + { + _typeReaders[typeof(T)] = reader; + } + public void AddTypeReader(Type type, TypeReader reader) + { + _typeReaders[type] = reader; + } + internal TypeReader GetTypeReader(Type type) + { + TypeReader reader; + if (_typeReaders.TryGetValue(type, out reader)) + return reader; + return null; + } + + //Execution public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); public SearchResult Search(CommandContext context, string input) { diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index a5a1f8bc4..e809c1b70 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -16,7 +16,7 @@ namespace Discord.Commands _nodes = new ConcurrentDictionary(); } - public void AddCommand(Command command) + public void AddCommand(CommandInfo command) { foreach (string text in command.Aliases) { @@ -35,7 +35,7 @@ namespace Discord.Commands } } } - public void RemoveCommand(Command command) + public void RemoveCommand(CommandInfo command) { foreach (string text in command.Aliases) { @@ -60,7 +60,7 @@ namespace Discord.Commands } } - public IEnumerable GetCommands(string text) + public IEnumerable GetCommands(string text) { int nextSpace = NextWhitespace(text); string name; @@ -76,7 +76,7 @@ namespace Discord.Commands if (_nodes.TryGetValue(name, out nextNode)) return nextNode.GetCommands(text, nextSpace + 1); else - return Enumerable.Empty(); + return Enumerable.Empty(); } } diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index 5ef42544e..a348a8cb4 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -9,7 +9,7 @@ namespace Discord.Commands private readonly ConcurrentDictionary _nodes; private readonly string _name; private readonly object _lockObj = new object(); - private ImmutableArray _commands; + private ImmutableArray _commands; public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; @@ -17,10 +17,10 @@ namespace Discord.Commands { _name = name; _nodes = new ConcurrentDictionary(); - _commands = ImmutableArray.Create(); + _commands = ImmutableArray.Create(); } - public void AddCommand(string text, int index, Command command) + public void AddCommand(string text, int index, CommandInfo command) { int nextSpace = text.IndexOf(' ', index); string name; @@ -41,7 +41,7 @@ namespace Discord.Commands } } } - public void RemoveCommand(string text, int index, Command command) + public void RemoveCommand(string text, int index, CommandInfo command) { int nextSpace = text.IndexOf(' ', index); string name; @@ -68,7 +68,7 @@ namespace Discord.Commands } } - public IEnumerable GetCommands(string text, int index) + public IEnumerable GetCommands(string text, int index) { int nextSpace = text.IndexOf(' ', index); string name; diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs new file mode 100644 index 000000000..544e37f65 --- /dev/null +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public abstract class ModuleBase + { + public IDiscordClient Client { get; internal set; } + public CommandContext Context { get; internal set; } + + protected virtual async Task ReplyAsync(string message, bool isTTS = false, RequestOptions options = null) + { + await Context.Channel.SendMessageAsync(message, isTTS, options).ConfigureAwait(false); + } + } +} diff --git a/src/Discord.Net.Commands/Module.cs b/src/Discord.Net.Commands/ModuleInfo.cs similarity index 62% rename from src/Discord.Net.Commands/Module.cs rename to src/Discord.Net.Commands/ModuleInfo.cs index 9d61ca522..c061e3de4 100644 --- a/src/Discord.Net.Commands/Module.cs +++ b/src/Discord.Net.Commands/ModuleInfo.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Reflection; @@ -6,26 +7,31 @@ using System.Reflection; namespace Discord.Commands { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class Module + public class ModuleInfo { + internal readonly Func _builder; + public TypeInfo Source { get; } public CommandService Service { get; } public string Name { get; } public string Prefix { get; } public string Summary { get; } public string Remarks { get; } - public IEnumerable Commands { get; } - internal object Instance { get; } - + public IEnumerable Commands { get; } public IReadOnlyList Preconditions { get; } - internal Module(TypeInfo source, CommandService service, object instance, ModuleAttribute moduleAttr, IDependencyMap dependencyMap) + internal ModuleInfo(TypeInfo source, CommandService service, IDependencyMap dependencyMap) { Source = source; Service = service; Name = source.Name; - Prefix = moduleAttr.Prefix ?? ""; - Instance = instance; + _builder = ReflectionUtils.CreateBuilder(source, Service, dependencyMap); + + var groupAttr = source.GetCustomAttribute(); + if (groupAttr != null) + Prefix = groupAttr.Prefix; + else + Prefix = ""; var nameAttr = source.GetCustomAttribute(); if (nameAttr != null) @@ -39,20 +45,19 @@ namespace Discord.Commands if (remarksAttr != null) Remarks = remarksAttr.Text; - List commands = new List(); - SearchClass(source, instance, commands, Prefix, dependencyMap); + List commands = new List(); + SearchClass(source, commands, Prefix, dependencyMap); Commands = commands; - Preconditions = BuildPreconditions(); + Preconditions = Source.GetCustomAttributes().ToImmutableArray(); } - - private void SearchClass(TypeInfo parentType, object instance, List commands, string groupPrefix, IDependencyMap dependencyMap) + private void SearchClass(TypeInfo parentType, List commands, string groupPrefix, IDependencyMap dependencyMap) { foreach (var method in parentType.DeclaredMethods) { var cmdAttr = method.GetCustomAttribute(); if (cmdAttr != null) - commands.Add(new Command(method, this, instance, cmdAttr, groupPrefix)); + commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix)); } foreach (var type in parentType.DeclaredNestedTypes) { @@ -66,15 +71,13 @@ namespace Discord.Commands else nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); - SearchClass(type, ReflectionUtils.CreateObject(type, Service, dependencyMap), commands, nextGroupPrefix, dependencyMap); + SearchClass(type, commands, nextGroupPrefix, dependencyMap); } } } - private IReadOnlyList BuildPreconditions() - { - return Source.GetCustomAttributes().ToImmutableArray(); - } + internal ModuleBase CreateInstance() + => _builder(); public override string ToString() => Name; private string DebuggerDisplay => Name; diff --git a/src/Discord.Net.Commands/ReflectionUtils.cs b/src/Discord.Net.Commands/ReflectionUtils.cs index 4de883731..e84a037ef 100644 --- a/src/Discord.Net.Commands/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/ReflectionUtils.cs @@ -6,7 +6,10 @@ namespace Discord.Commands { internal class ReflectionUtils { - internal static object CreateObject(TypeInfo typeInfo, CommandService service, IDependencyMap map = null) + internal static T CreateObject(TypeInfo typeInfo, CommandService service, IDependencyMap map = null) + => CreateBuilder(typeInfo, service, map)(); + + internal static Func CreateBuilder(TypeInfo typeInfo, CommandService service, IDependencyMap map = null) { var constructors = typeInfo.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); if (constructors.Length == 0) @@ -14,7 +17,7 @@ namespace Discord.Commands else if (constructors.Length > 1) throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); - var constructor = constructors[0]; + var constructor = constructors[0]; ParameterInfo[] parameters = constructor.GetParameters(); object[] args = new object[parameters.Length]; @@ -34,14 +37,17 @@ namespace Discord.Commands args[i] = arg; } - try - { - return constructor.Invoke(args); - } - catch (Exception ex) + return () => { - throw new Exception($"Failed to create \"{typeInfo.FullName}\"", ex); - } + try + { + return (T)constructor.Invoke(args); + } + catch (Exception ex) + { + throw new Exception($"Failed to create \"{typeInfo.FullName}\"", ex); + } + }; } } } diff --git a/src/Discord.Net.Commands/Results/SearchResult.cs b/src/Discord.Net.Commands/Results/SearchResult.cs index 962834c03..17942b61a 100644 --- a/src/Discord.Net.Commands/Results/SearchResult.cs +++ b/src/Discord.Net.Commands/Results/SearchResult.cs @@ -7,14 +7,14 @@ namespace Discord.Commands public struct SearchResult : IResult { public string Text { get; } - public IReadOnlyList Commands { get; } + public IReadOnlyList Commands { get; } public CommandError? Error { get; } public string ErrorReason { get; } public bool IsSuccess => !Error.HasValue; - private SearchResult(string text, IReadOnlyList commands, CommandError? error, string errorReason) + private SearchResult(string text, IReadOnlyList commands, CommandError? error, string errorReason) { Text = text; Commands = commands; @@ -22,7 +22,7 @@ namespace Discord.Commands ErrorReason = errorReason; } - public static SearchResult FromSuccess(string text, IReadOnlyList 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); diff --git a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs index 3a7b39643..1ef105527 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -9,7 +9,7 @@ namespace Discord { //Based on https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Threading.Tasks.Parallel/src/System/Threading/PlatformHelper.cs //Copyright (c) .NET Foundation and Contributors - public static class ConcurrentHashSet + internal static class ConcurrentHashSet { private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; private static volatile int s_processorCount; From c7ac85455f9dfc8a22dcc28c00e7fcc28f1ad5f9 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 10 Oct 2016 21:16:34 -0300 Subject: [PATCH 096/102] Added a configurable run mode for commands --- .../Attributes/CommandAttribute.cs | 1 + src/Discord.Net.Commands/CommandInfo.cs | 16 +++++++++++++++- src/Discord.Net.Commands/RunMode.cs | 9 +++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.Commands/RunMode.cs diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index 014668405..baac75ff9 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -6,6 +6,7 @@ namespace Discord.Commands public class CommandAttribute : Attribute { public string Text { get; } + public RunMode RunMode { get; set; } = RunMode.Sync; public CommandAttribute() { diff --git a/src/Discord.Net.Commands/CommandInfo.cs b/src/Discord.Net.Commands/CommandInfo.cs index e63fb6d3f..4a4980d21 100644 --- a/src/Discord.Net.Commands/CommandInfo.cs +++ b/src/Discord.Net.Commands/CommandInfo.cs @@ -25,6 +25,7 @@ namespace Discord.Commands public string Text { get; } public int Priority { get; } public bool HasVarArgs { get; } + public RunMode RunMode { get; } public IReadOnlyList Aliases { get; } public IReadOnlyList Parameters { get; } public IReadOnlyList Preconditions { get; } @@ -40,6 +41,7 @@ namespace Discord.Commands if (attribute.Text == null) Text = groupPrefix; + RunMode = attribute.RunMode; if (groupPrefix != "") groupPrefix += " "; @@ -150,7 +152,19 @@ namespace Discord.Commands { try { - await _action.Invoke(context, GenerateArgs(argList, paramList)).ConfigureAwait(false);//Note: This code may need context + var args = GenerateArgs(argList, paramList); + switch (RunMode) + { + case RunMode.Sync: + await _action(context, args).ConfigureAwait(false); + break; + case RunMode.Async: + var t1 = _action(context, args); + break; + case RunMode.FireAndForget: + var t2 = Task.Run(() => _action(context, args)); + break; + } return ExecuteResult.FromSuccess(); } catch (Exception ex) diff --git a/src/Discord.Net.Commands/RunMode.cs b/src/Discord.Net.Commands/RunMode.cs new file mode 100644 index 000000000..0acdc1629 --- /dev/null +++ b/src/Discord.Net.Commands/RunMode.cs @@ -0,0 +1,9 @@ +namespace Discord.Commands +{ + public enum RunMode + { + Sync, + Async, + FireAndForget + } +} From d480b2f25b2a1e493e4d2433f45d919bcb9411db Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 10 Oct 2016 21:19:41 -0300 Subject: [PATCH 097/102] Renamed run modes --- src/Discord.Net.Commands/CommandInfo.cs | 4 ++-- src/Discord.Net.Commands/RunMode.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/CommandInfo.cs b/src/Discord.Net.Commands/CommandInfo.cs index 4a4980d21..8443ad2f4 100644 --- a/src/Discord.Net.Commands/CommandInfo.cs +++ b/src/Discord.Net.Commands/CommandInfo.cs @@ -158,10 +158,10 @@ namespace Discord.Commands case RunMode.Sync: await _action(context, args).ConfigureAwait(false); break; - case RunMode.Async: + case RunMode.Mixed: var t1 = _action(context, args); break; - case RunMode.FireAndForget: + case RunMode.Async: var t2 = Task.Run(() => _action(context, args)); break; } diff --git a/src/Discord.Net.Commands/RunMode.cs b/src/Discord.Net.Commands/RunMode.cs index 0acdc1629..0799f825c 100644 --- a/src/Discord.Net.Commands/RunMode.cs +++ b/src/Discord.Net.Commands/RunMode.cs @@ -3,7 +3,7 @@ public enum RunMode { Sync, - Async, - FireAndForget + Mixed, + Async } } From 3fc7b3b20622e54eeccdfa30a8ab86d58a80197c Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 10 Oct 2016 21:34:45 -0300 Subject: [PATCH 098/102] RPC's Authorize should not try to authenticate --- src/Discord.Net.Commands/CommandInfo.cs | 6 ++-- src/Discord.Net.Rpc/DiscordRpcClient.cs | 42 +++++++++++++++---------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Discord.Net.Commands/CommandInfo.cs b/src/Discord.Net.Commands/CommandInfo.cs index 8443ad2f4..75107a80c 100644 --- a/src/Discord.Net.Commands/CommandInfo.cs +++ b/src/Discord.Net.Commands/CommandInfo.cs @@ -155,13 +155,13 @@ namespace Discord.Commands var args = GenerateArgs(argList, paramList); switch (RunMode) { - case RunMode.Sync: + case RunMode.Sync: //Always sync await _action(context, args).ConfigureAwait(false); break; - case RunMode.Mixed: + case RunMode.Mixed: //Sync until first await statement var t1 = _action(context, args); break; - case RunMode.Async: + case RunMode.Async: //Always async var t2 = Task.Run(() => _action(context, args)); break; } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index d0443d15e..52fe6172f 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -385,25 +385,33 @@ namespace Discord.Rpc //CancellationToken = _cancelToken //TODO: Implement }; - var _ = Task.Run(async () => + if (ApiClient.LoginState == LoginState.LoggedIn) { - try + var _ = Task.Run(async () => { - var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); - CurrentUser = RestSelfUser.Create(this, response.User); - CurrentApplication = RestApplication.Create(this, response.Application); - Scopes = response.Scopes; - TokenExpiresAt = response.Expires; - - var __ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete - await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false); - } - catch (Exception ex) - { - await _rpcLogger.ErrorAsync($"Error handling {cmd}{(evnt.IsSpecified ? $" ({evnt})" : "")}", ex).ConfigureAwait(false); - return; - } - }); + try + { + var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); + CurrentUser = RestSelfUser.Create(this, response.User); + CurrentApplication = RestApplication.Create(this, response.Application); + Scopes = response.Scopes; + TokenExpiresAt = response.Expires; + + var __ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete + await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false); + } + catch (Exception ex) + { + await _rpcLogger.ErrorAsync($"Error handling {cmd}{(evnt.IsSpecified ? $" ({evnt})" : "")}", ex).ConfigureAwait(false); + return; + } + }); + } + else + { + var _ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete + await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false); + } } break; From 2fd2e2617958bec9685ae2baa7bb3cfe3f549b86 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 10 Oct 2016 22:10:51 -0300 Subject: [PATCH 099/102] Fixed missing private channels --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 73e183da7..618fcf5e3 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1593,7 +1593,9 @@ namespace Discord.WebSocket internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state) { - return SocketChannel.CreatePrivate(this, state, model); + var channel = SocketChannel.CreatePrivate(this, state, model); + state.AddChannel(channel as SocketChannel); + return channel; } internal ISocketPrivateChannel RemovePrivateChannel(ulong id) { From 37f0d8c7e84e25779a8f23f21f1fc11b259165b5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 10 Oct 2016 22:11:07 -0300 Subject: [PATCH 100/102] Fixed nullrefs when message cache is disabled --- .../Entities/Channels/SocketDMChannel.cs | 4 ++-- .../Entities/Channels/SocketGroupChannel.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 46c6aca85..1e20f12d8 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -82,9 +82,9 @@ namespace Discord.WebSocket => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) - => _messages.Add(msg); + => _messages?.Add(msg); internal SocketMessage RemoveMessage(ulong id) - => _messages.Remove(id); + => _messages?.Remove(id); //Users public new SocketUser GetUser(ulong id) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 4fa366704..450460d8b 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -105,9 +105,9 @@ namespace Discord.WebSocket => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) - => _messages.Add(msg); + => _messages?.Add(msg); internal SocketMessage RemoveMessage(ulong id) - => _messages.Remove(id); + => _messages?.Remove(id); //Users public new SocketGroupUser GetUser(ulong id) From bf10529f80cb4169767dc54063a3bdda94992a33 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 11 Oct 2016 23:13:28 -0300 Subject: [PATCH 101/102] Added new rate limits --- .../API/DiscordRestApiClient.cs | 380 +++++++++++++----- src/Discord.Net.Core/Logging/LogManager.cs | 1 - .../Net/Queue/ClientBucket.cs | 26 ++ .../Net/Queue/RequestQueue.cs | 114 ++++-- .../Net/Queue/RequestQueueBucket.cs | 283 ++++++++----- .../Net/Queue/Requests/IQueuedRequest.cs | 14 - .../Net/Queue/Requests/IRequest.cs | 12 + .../Net/Queue/Requests/JsonRestRequest.cs | 7 +- .../Queue/Requests/MultipartRestRequest.cs | 7 +- .../Net/Queue/Requests/RestRequest.cs | 12 +- .../Net/Queue/Requests/WebSocketRequest.cs | 13 +- .../Net/RateLimitException.cs | 17 - src/Discord.Net.Core/Net/RateLimitInfo.cs | 24 ++ .../Net/RateLimitedException.cs | 12 + .../Net/Rest/DefaultRestClient.cs | 50 +-- src/Discord.Net.Core/Net/Rest/IRestClient.cs | 8 +- src/Discord.Net.Core/Net/Rest/RestResponse.cs | 20 + src/Discord.Net.Core/RequestOptions.cs | 1 - src/Discord.Net.Rest/BaseDiscordClient.cs | 9 +- .../API/DiscordRpcApiClient.cs | 2 +- .../API/DiscordSocketApiClient.cs | 4 +- 21 files changed, 682 insertions(+), 334 deletions(-) create mode 100644 src/Discord.Net.Core/Net/Queue/ClientBucket.cs delete mode 100644 src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs create mode 100644 src/Discord.Net.Core/Net/Queue/Requests/IRequest.cs delete mode 100644 src/Discord.Net.Core/Net/RateLimitException.cs create mode 100644 src/Discord.Net.Core/Net/RateLimitInfo.cs create mode 100644 src/Discord.Net.Core/Net/RateLimitedException.cs create mode 100644 src/Discord.Net.Core/Net/Rest/RestResponse.cs diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index baa0698d8..e2fcbf9af 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -6,13 +6,16 @@ using Discord.Net.Queue; using Discord.Net.Rest; using Newtonsoft.Json; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Net; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -21,9 +24,11 @@ namespace Discord.API { public class DiscordRestApiClient : IDisposable { + private static readonly ConcurrentDictionary> _bucketIdGenerators = new ConcurrentDictionary>(); + public event Func SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } private readonly AsyncEvent> _sentRequestEvent = new AsyncEvent>(); - + protected readonly JsonSerializer _serializer; protected readonly SemaphoreSlim _stateLock; private readonly RestClientProvider _restClientProvider; @@ -112,6 +117,7 @@ namespace Discord.API _restClient.SetCancelToken(_loginCancelToken.Token); AuthTokenType = tokenType; + RequestQueue.TokenType = tokenType; _authToken = token; _restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken)); @@ -159,42 +165,61 @@ namespace Discord.API internal virtual Task DisconnectInternalAsync() => Task.CompletedTask; //Core - public async Task SendAsync(string method, string endpoint, RequestOptions options = null) + public async Task SendAsync(string method, string endpoint, string bucketId, RequestOptions options) { options.HeaderOnly = true; - var request = new RestRequest(_restClient, method, endpoint, options); + var request = new RestRequest(_restClient, method, endpoint, bucketId, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); } - public async Task SendJsonAsync(string method, string endpoint, object payload, RequestOptions options = null) + public async Task SendJsonAsync(string method, string endpoint, string bucketId, object payload, RequestOptions options) { options.HeaderOnly = true; var json = payload != null ? SerializeJson(payload) : null; - var request = new JsonRestRequest(_restClient, method, endpoint, json, options); + var request = new JsonRestRequest(_restClient, method, endpoint, bucketId, json, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); } - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, RequestOptions options = null) + public async Task SendMultipartAsync(string method, string endpoint, string bucketId, IReadOnlyDictionary multipartArgs, RequestOptions options) { options.HeaderOnly = true; - var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); + var request = new MultipartRestRequest(_restClient, method, endpoint, bucketId, multipartArgs, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); } - public async Task SendAsync(string method, string endpoint, RequestOptions options = null) where TResponse : class + public async Task SendAsync(string method, string endpoint, string bucketId, RequestOptions options) where TResponse : class { - var request = new RestRequest(_restClient, method, endpoint, options); + var request = new RestRequest(_restClient, method, endpoint, bucketId, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } - public async Task SendJsonAsync(string method, string endpoint, object payload, RequestOptions options = null) where TResponse : class + public async Task SendJsonAsync(string method, string endpoint, string bucketId, object payload, RequestOptions options) where TResponse : class { var json = payload != null ? SerializeJson(payload) : null; - var request = new JsonRestRequest(_restClient, method, endpoint, json, options); + var request = new JsonRestRequest(_restClient, method, endpoint, bucketId, json, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, RequestOptions options = null) + public async Task SendMultipartAsync(string method, string endpoint, string bucketId, IReadOnlyDictionary multipartArgs, RequestOptions options) { - var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); + var request = new MultipartRestRequest(_restClient, method, endpoint, bucketId, multipartArgs, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } - + + internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, + RequestOptions options, [CallerMemberName] string funcName = null) + => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, funcName), options); + internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, + RequestOptions options, [CallerMemberName] string funcName = null) + => SendJsonAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, funcName), payload, options); + internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, + RequestOptions options, [CallerMemberName] string funcName = null) + => SendMultipartAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, funcName), multipartArgs, options); + internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, + RequestOptions options, [CallerMemberName] string funcName = null) where TResponse : class + => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, funcName), options); + internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, + RequestOptions options, [CallerMemberName] string funcName = null) where TResponse : class + => SendJsonAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, funcName), payload, options); + internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, + RequestOptions options, [CallerMemberName] string funcName = null) + => SendMultipartAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, funcName), multipartArgs, options); + private async Task SendInternalAsync(string method, string endpoint, RestRequest request) { if (!request.Options.IgnoreState) @@ -214,7 +239,7 @@ namespace Discord.API public async Task ValidateTokenAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - await SendAsync("GET", "auth/login", options: options).ConfigureAwait(false); + await SendAsync("GET", () => "auth/login", new BucketIds(), options: options).ConfigureAwait(false); } //Channels @@ -225,7 +250,8 @@ namespace Discord.API try { - return await SendAsync("GET", $"channels/{channelId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendAsync("GET", () => $"channels/{channelId}", ids, options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } } @@ -237,7 +263,8 @@ namespace Discord.API try { - var model = await SendAsync("GET", $"channels/{channelId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + var model = await SendAsync("GET", () => $"channels/{channelId}", ids, options: options).ConfigureAwait(false); if (!model.GuildId.IsSpecified || model.GuildId.Value != guildId) return null; return model; @@ -249,7 +276,8 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", $"guilds/{guildId}/channels", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/channels", ids, options: options).ConfigureAwait(false); } public async Task CreateGuildChannelAsync(ulong guildId, CreateGuildChannelParams args, RequestOptions options = null) { @@ -259,14 +287,16 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("POST", $"guilds/{guildId}/channels", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: 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); - return await SendAsync("DELETE", $"channels/{channelId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendAsync("DELETE", () => $"channels/{channelId}", ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildChannelAsync(ulong channelId, ModifyGuildChannelParams args, RequestOptions options = null) { @@ -276,7 +306,8 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildChannelAsync(ulong channelId, ModifyTextChannelParams args, RequestOptions options = null) { @@ -286,7 +317,8 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildChannelAsync(ulong channelId, ModifyVoiceChannelParams args, RequestOptions options = null) { @@ -298,7 +330,8 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"channels/{channelId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildChannelsAsync(ulong guildId, IEnumerable args, RequestOptions options = null) { @@ -315,7 +348,8 @@ namespace Discord.API await ModifyGuildChannelAsync(channels[0].Id, new ModifyGuildChannelParams { Position = channels[0].Position }).ConfigureAwait(false); break; default: - await SendJsonAsync("PATCH", $"guilds/{guildId}/channels", channels, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/channels", channels, ids, options: options).ConfigureAwait(false); break; } } @@ -329,7 +363,8 @@ namespace Discord.API try { - return await SendAsync("GET", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendAsync("GET", () => $"channels/{channelId}/messages/{messageId}", ids, options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } } @@ -359,12 +394,13 @@ namespace Discord.API break; } - string endpoint; + var ids = new BucketIds(channelId: channelId); + Expression> endpoint; if (relativeId != null) - endpoint = $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; + endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; else - endpoint = $"channels/{channelId}/messages?limit={limit}"; - return await SendAsync>("GET", endpoint, options: options).ConfigureAwait(false); + endpoint = () =>$"channels/{channelId}/messages?limit={limit}"; + return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } public async Task CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) { @@ -375,7 +411,8 @@ namespace Discord.API 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", $"channels/{channelId}/messages", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, options: options).ConfigureAwait(false); } public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) { @@ -393,7 +430,8 @@ namespace Discord.API throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); } - return await SendMultipartAsync("POST", $"channels/{channelId}/messages", args.ToDictionary(), options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendMultipartAsync("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, options: options).ConfigureAwait(false); } public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { @@ -401,7 +439,8 @@ namespace Discord.API Preconditions.NotEqual(messageId, 0, nameof(messageId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("DELETE", $"channels/{channelId}/messages/{messageId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}", ids, options: options).ConfigureAwait(false); } public async Task DeleteMessagesAsync(ulong channelId, DeleteMessagesParams args, RequestOptions options = null) { @@ -419,7 +458,8 @@ namespace Discord.API await DeleteMessageAsync(channelId, args.MessageIds[0]).ConfigureAwait(false); break; default: - await SendJsonAsync("POST", $"channels/{channelId}/messages/bulk-delete", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + await SendJsonAsync("POST", () => $"channels/{channelId}/messages/bulk-delete", args, ids, options: options).ConfigureAwait(false); break; } } @@ -436,7 +476,8 @@ namespace Discord.API } options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"channels/{channelId}/messages/{messageId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, options: options).ConfigureAwait(false); } public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { @@ -444,14 +485,16 @@ namespace Discord.API Preconditions.NotEqual(messageId, 0, nameof(messageId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("POST", $"channels/{channelId}/messages/{messageId}/ack", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + 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)); options = RequestOptions.CreateOrClone(options); - await SendAsync("POST", $"channels/{channelId}/typing", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + await SendAsync("POST", () => $"channels/{channelId}/typing", ids, options: options).ConfigureAwait(false); } //Channel Permissions @@ -462,7 +505,8 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); options = RequestOptions.CreateOrClone(options); - await SendJsonAsync("PUT", $"channels/{channelId}/permissions/{targetId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + await SendJsonAsync("PUT", () => $"channels/{channelId}/permissions/{targetId}", args, ids, options: options).ConfigureAwait(false); } public async Task DeleteChannelPermissionAsync(ulong channelId, ulong targetId, RequestOptions options = null) { @@ -470,7 +514,8 @@ namespace Discord.API Preconditions.NotEqual(targetId, 0, nameof(targetId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("DELETE", $"channels/{channelId}/permissions/{targetId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + await SendAsync("DELETE", () => $"channels/{channelId}/permissions/{targetId}", ids, options: options).ConfigureAwait(false); } //Channel Pins @@ -480,7 +525,8 @@ namespace Discord.API Preconditions.GreaterThan(messageId, 0, nameof(messageId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("PUT", $"channels/{channelId}/pins/{messageId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + await SendAsync("PUT", () => $"channels/{channelId}/pins/{messageId}", ids, options: options).ConfigureAwait(false); } public async Task RemovePinAsync(ulong channelId, ulong messageId, RequestOptions options = null) @@ -489,14 +535,16 @@ namespace Discord.API Preconditions.NotEqual(messageId, 0, nameof(messageId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("DELETE", $"channels/{channelId}/pins/{messageId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + 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); - return await SendAsync>("GET", $"channels/{channelId}/pins", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendAsync>("GET", () => $"channels/{channelId}/pins", ids, options: options).ConfigureAwait(false); } //Channel Recipients @@ -506,7 +554,8 @@ namespace Discord.API Preconditions.GreaterThan(userId, 0, nameof(userId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("PUT", $"channels/{channelId}/recipients/{userId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + await SendAsync("PUT", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); } public async Task RemoveGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null) @@ -515,7 +564,8 @@ namespace Discord.API Preconditions.NotEqual(userId, 0, nameof(userId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("DELETE", $"channels/{channelId}/recipients/{userId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + await SendAsync("DELETE", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); } //Guilds @@ -526,7 +576,8 @@ namespace Discord.API try { - return await SendAsync("GET", $"guilds/{guildId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync("GET", () => $"guilds/{guildId}", ids, options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } } @@ -536,22 +587,24 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); options = RequestOptions.CreateOrClone(options); - - return await SendJsonAsync("POST", "guilds", args, 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); - return await SendAsync("DELETE", $"guilds/{guildId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: 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); - return await SendAsync("DELETE", $"users/@me/guilds/{guildId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync("DELETE", () => $"users/@me/guilds/{guildId}", ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildAsync(ulong guildId, ModifyGuildParams args, RequestOptions options = null) { @@ -564,7 +617,8 @@ namespace Discord.API Preconditions.NotNull(args.RegionId, nameof(args.RegionId)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"guilds/{guildId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}", args, ids, options: options).ConfigureAwait(false); } public async Task BeginGuildPruneAsync(ulong guildId, GuildPruneParams args, RequestOptions options = null) { @@ -573,7 +627,8 @@ namespace Discord.API Preconditions.AtLeast(args.Days, 0, nameof(args.Days)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("POST", $"guilds/{guildId}/prune", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: 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) { @@ -582,7 +637,8 @@ namespace Discord.API Preconditions.AtLeast(args.Days, 0, nameof(args.Days)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("GET", $"guilds/{guildId}/prune", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendJsonAsync("GET", () => $"guilds/{guildId}/prune", args, ids, options: options).ConfigureAwait(false); } //Guild Bans @@ -591,7 +647,8 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", $"guilds/{guildId}/bans", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false); } public async Task CreateGuildBanAsync(ulong guildId, ulong userId, CreateGuildBanParams args, RequestOptions options = null) { @@ -601,7 +658,8 @@ namespace Discord.API Preconditions.AtLeast(args.DeleteMessageDays, 0, nameof(args.DeleteMessageDays)); options = RequestOptions.CreateOrClone(options); - await SendAsync("PUT", $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}", ids, options: options).ConfigureAwait(false); } public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) { @@ -609,7 +667,8 @@ namespace Discord.API Preconditions.NotEqual(userId, 0, nameof(userId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("DELETE", $"guilds/{guildId}/bans/{userId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); } //Guild Embeds @@ -620,7 +679,8 @@ namespace Discord.API try { - return await SendAsync("GET", $"guilds/{guildId}/embed", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/embed", ids, options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } } @@ -630,7 +690,8 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"guilds/{guildId}/embed", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/embed", args, ids, options: options).ConfigureAwait(false); } //Guild Integrations @@ -639,7 +700,8 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", $"guilds/{guildId}/integrations", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); } public async Task CreateGuildIntegrationAsync(ulong guildId, CreateGuildIntegrationParams args, RequestOptions options = null) { @@ -648,7 +710,8 @@ namespace Discord.API Preconditions.NotEqual(args.Id, 0, nameof(args.Id)); options = RequestOptions.CreateOrClone(options); - return await SendAsync("POST", $"guilds/{guildId}/integrations", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync("POST", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); } public async Task DeleteGuildIntegrationAsync(ulong guildId, ulong integrationId, RequestOptions options = null) { @@ -656,7 +719,8 @@ namespace Discord.API Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync("DELETE", $"guilds/{guildId}/integrations/{integrationId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync("DELETE", () => $"guilds/{guildId}/integrations/{integrationId}", ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildIntegrationAsync(ulong guildId, ulong integrationId, ModifyGuildIntegrationParams args, RequestOptions options = null) { @@ -667,7 +731,8 @@ namespace Discord.API Preconditions.AtLeast(args.ExpireGracePeriod, 0, nameof(args.ExpireGracePeriod)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: 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) { @@ -675,7 +740,8 @@ namespace Discord.API Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync("POST", $"guilds/{guildId}/integrations/{integrationId}/sync", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync("POST", () => $"guilds/{guildId}/integrations/{integrationId}/sync", ids, options: options).ConfigureAwait(false); } //Guild Invites @@ -694,7 +760,7 @@ namespace Discord.API try { - return await SendAsync("GET", $"invites/{inviteId}", options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } } @@ -703,14 +769,16 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", $"guilds/{guildId}/invites", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/invites", ids, options: options).ConfigureAwait(false); } public async Task> GetChannelInvitesAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", $"channels/{channelId}/invites", options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendAsync>("GET", () => $"channels/{channelId}/invites", ids, options: options).ConfigureAwait(false); } public async Task CreateChannelInviteAsync(ulong channelId, CreateChannelInviteParams args, RequestOptions options = null) { @@ -720,21 +788,22 @@ namespace Discord.API Preconditions.AtLeast(args.MaxUses, 0, nameof(args.MaxUses)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("POST", $"channels/{channelId}/invites", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(channelId: channelId); + return await SendJsonAsync("POST", () => $"channels/{channelId}/invites", args, ids, options: options).ConfigureAwait(false); } - public async Task DeleteInviteAsync(string inviteCode, RequestOptions options = null) + public async Task DeleteInviteAsync(string inviteId, RequestOptions options = null) { - Preconditions.NotNullOrEmpty(inviteCode, nameof(inviteCode)); + Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); - - return await SendAsync("DELETE", $"invites/{inviteCode}", options: options).ConfigureAwait(false); + + return await SendAsync("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); } - public async Task AcceptInviteAsync(string inviteCode, RequestOptions options = null) + public async Task AcceptInviteAsync(string inviteId, RequestOptions options = null) { - Preconditions.NotNullOrEmpty(inviteCode, nameof(inviteCode)); + Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); - - await SendAsync("POST", $"invites/{inviteCode}", options: options).ConfigureAwait(false); + + await SendAsync("POST", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); } //Guild Members @@ -746,7 +815,8 @@ namespace Discord.API try { - return await SendAsync("GET", $"guilds/{guildId}/members/{userId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/members/{userId}", ids, options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } } @@ -761,9 +831,10 @@ namespace Discord.API int limit = args.Limit.GetValueOrDefault(int.MaxValue); ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); - - string endpoint = $"guilds/{guildId}/members?limit={limit}&after={afterUserId}"; - return await SendAsync>("GET", endpoint, options: options).ConfigureAwait(false); + + var ids = new BucketIds(guildId: guildId); + Expression> endpoint = () => $"guilds/{guildId}/members?limit={limit}&after={afterUserId}"; + return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null) { @@ -771,7 +842,8 @@ namespace Discord.API Preconditions.NotEqual(userId, 0, nameof(userId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("DELETE", $"guilds/{guildId}/members/{userId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}", ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildMemberAsync(ulong guildId, ulong userId, ModifyGuildMemberParams args, RequestOptions options = null) { @@ -790,7 +862,8 @@ namespace Discord.API } if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified) { - await SendJsonAsync("PATCH", $"guilds/{guildId}/members/{userId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/{userId}", args, ids, options: options).ConfigureAwait(false); } } @@ -800,14 +873,16 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", $"guilds/{guildId}/roles", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: 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); - return await SendAsync("POST", $"guilds/{guildId}/roles", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync("POST", () => $"guilds/{guildId}/roles", ids, options: options).ConfigureAwait(false); } public async Task DeleteGuildRoleAsync(ulong guildId, ulong roleId, RequestOptions options = null) { @@ -815,7 +890,8 @@ namespace Discord.API Preconditions.NotEqual(roleId, 0, nameof(roleId)); options = RequestOptions.CreateOrClone(options); - await SendAsync("DELETE", $"guilds/{guildId}/roles/{roleId}", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + await SendAsync("DELETE", () => $"guilds/{guildId}/roles/{roleId}", ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildRoleAsync(ulong guildId, ulong roleId, ModifyGuildRoleParams args, RequestOptions options = null) { @@ -827,7 +903,8 @@ namespace Discord.API Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"guilds/{guildId}/roles/{roleId}", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: 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) { @@ -843,7 +920,8 @@ namespace Discord.API case 1: return ImmutableArray.Create(await ModifyGuildRoleAsync(guildId, roles[0].Id, roles[0]).ConfigureAwait(false)); default: - return await SendJsonAsync>("PATCH", $"guilds/{guildId}/roles", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendJsonAsync>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); } } @@ -855,7 +933,7 @@ namespace Discord.API try { - return await SendAsync("GET", $"users/{userId}", options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"users/{userId}", new BucketIds(), options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return null; } } @@ -864,27 +942,27 @@ namespace Discord.API public async Task GetMyUserAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", "users/@me", 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", 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", options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => "users/@me/channels", new BucketIds(), options: options).ConfigureAwait(false); } public async Task> GetMyGuildsAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", "users/@me/guilds", options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => "users/@me/guilds", new BucketIds(), options: options).ConfigureAwait(false); } public async Task GetMyApplicationAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", "oauth2/applications/@me", options: options).ConfigureAwait(false); + return await SendAsync("GET", () => "oauth2/applications/@me", new BucketIds(), options: options).ConfigureAwait(false); } public async Task ModifySelfAsync(ModifyCurrentUserParams args, RequestOptions options = null) { @@ -892,7 +970,7 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Username, nameof(args.Username)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", "users/@me", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => "users/@me", args, new BucketIds(), options: options).ConfigureAwait(false); } public async Task ModifyMyNickAsync(ulong guildId, ModifyCurrentUserNickParams args, RequestOptions options = null) { @@ -900,7 +978,8 @@ namespace Discord.API Preconditions.NotNull(args.Nickname, nameof(args.Nickname)); options = RequestOptions.CreateOrClone(options); - await SendJsonAsync("PATCH", $"guilds/{guildId}/members/@me/nick", args, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/@me/nick", args, ids, options: options).ConfigureAwait(false); } public async Task CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null) { @@ -908,21 +987,22 @@ namespace Discord.API Preconditions.GreaterThan(args.RecipientId, 0, nameof(args.RecipientId)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("POST", $"users/@me/channels", args, 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", 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) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", $"guilds/{guildId}/regions", options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId: guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false); } //Helpers @@ -946,5 +1026,115 @@ namespace Discord.API using (JsonReader reader = new JsonTextReader(text)) return _serializer.Deserialize(reader); } + internal string GetBucketId(ulong guildId = 0, ulong channelId = 0, [CallerMemberName] string methodName = "") + { + if (guildId != 0) + { + if (channelId != 0) + return $"{methodName}({guildId}/{channelId})"; + else + return $"{methodName}({guildId})"; + } + else if (channelId != 0) + return $"{methodName}({channelId})"; + return $"{methodName}()"; + } + + internal class BucketIds + { + public ulong GuildId { get; } + public ulong ChannelId { get; } + + 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 string GetEndpoint(Expression> endpointExpr) + { + return endpointExpr.Compile()(); + } + private string GetBucketId(BucketIds ids, Expression> endpointExpr, string callingMethod) + { + return _bucketIdGenerators.GetOrAdd(callingMethod, x => CreateBucketId(endpointExpr))(ids); + } + + private Func CreateBucketId(Expression> endpoint) + { + try + { + //Is this a constant string? + if (endpoint.Body.NodeType == ExpressionType.Constant) + return x => (endpoint.Body as ConstantExpression).Value.ToString(); + + var builder = new StringBuilder(); + var methodCall = endpoint.Body as MethodCallExpression; + var methodArgs = methodCall.Arguments.ToArray(); + string format = (methodArgs[0] as ConstantExpression).Value as string; + + int endIndex = format.IndexOf('?'); //Dont include params + if (endIndex == -1) + endIndex = format.Length; + + int lastIndex = 0; + while (true) + { + int leftIndex = format.IndexOf("{", lastIndex); + if (leftIndex == -1 || leftIndex > endIndex) + { + builder.Append(format, lastIndex, endIndex - lastIndex); + break; + } + builder.Append(format, lastIndex, leftIndex); + 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[rightIndex + 1] == '/') //Ignore the next slash + rightIndex++; + + if (mappedId.HasValue) + builder.Append($"{{{mappedId.Value}}}"); + + lastIndex = rightIndex + 1; + } + + format = builder.ToString(); + return x => string.Format(format, x.ToArray()); + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to generate the bucket id for this operation", ex); + } + } + + private static string GetFieldName(Expression expr) + { + if (expr.NodeType == ExpressionType.Convert) + expr = (expr as UnaryExpression).Operand; + + if (expr.NodeType != ExpressionType.MemberAccess) + throw new InvalidOperationException("Unsupported expression"); + + return (expr as MemberExpression).Member.Name; + } } } diff --git a/src/Discord.Net.Core/Logging/LogManager.cs b/src/Discord.Net.Core/Logging/LogManager.cs index 3b7a4b960..104e02835 100644 --- a/src/Discord.Net.Core/Logging/LogManager.cs +++ b/src/Discord.Net.Core/Logging/LogManager.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using System.Threading.Tasks; namespace Discord.Logging diff --git a/src/Discord.Net.Core/Net/Queue/ClientBucket.cs b/src/Discord.Net.Core/Net/Queue/ClientBucket.cs new file mode 100644 index 000000000..93e5cfd23 --- /dev/null +++ b/src/Discord.Net.Core/Net/Queue/ClientBucket.cs @@ -0,0 +1,26 @@ +using System.Collections.Immutable; + +namespace Discord.Net.Queue +{ + public struct ClientBucket + { + private static readonly ImmutableDictionary _defs; + static ClientBucket() + { + var builder = ImmutableDictionary.CreateBuilder(); + builder.Add("", new ClientBucket(5, 5)); + _defs = builder.ToImmutable(); + } + + public static ClientBucket Get(string id) => _defs[id]; + + public int WindowCount { get; } + public int WindowSeconds { get; } + + public ClientBucket(int count, int seconds) + { + WindowCount = count; + WindowSeconds = seconds; + } + } +} diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueue.cs b/src/Discord.Net.Core/Net/Queue/RequestQueue.cs index d25c1f340..28caca1c2 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueue.cs @@ -1,85 +1,127 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Discord.Net.Queue { - public class RequestQueue + public class RequestQueue : IDisposable { - public event Func RateLimitTriggered; - - private readonly SemaphoreSlim _lock; - private readonly ConcurrentDictionary _buckets; + public event Func RateLimitTriggered; + + internal TokenType TokenType { get; set; } + + private readonly ConcurrentDictionary _buckets; + private readonly SemaphoreSlim _tokenLock; private CancellationTokenSource _clearToken; private CancellationToken _parentToken; - private CancellationToken _cancelToken; + private CancellationToken _requestCancelToken; //Parent token + Clear token + private CancellationTokenSource _cancelToken; //Dispose token + private DateTimeOffset _waitUntil; + + private Task _cleanupTask; public RequestQueue() { - _lock = new SemaphoreSlim(1, 1); + _tokenLock = new SemaphoreSlim(1, 1); _clearToken = new CancellationTokenSource(); - _cancelToken = CancellationToken.None; + _cancelToken = new CancellationTokenSource(); + _requestCancelToken = CancellationToken.None; _parentToken = CancellationToken.None; + + _buckets = new ConcurrentDictionary(); - _buckets = new ConcurrentDictionary(); + _cleanupTask = RunCleanup(); } + public async Task SetCancelTokenAsync(CancellationToken cancelToken) { - await _lock.WaitAsync().ConfigureAwait(false); + await _tokenLock.WaitAsync().ConfigureAwait(false); try { _parentToken = cancelToken; - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token).Token; + _requestCancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token).Token; + } + finally { _tokenLock.Release(); } + } + public async Task ClearAsync() + { + await _tokenLock.WaitAsync().ConfigureAwait(false); + try + { + _clearToken?.Cancel(); + _clearToken = new CancellationTokenSource(); + if (_parentToken != null) + _requestCancelToken = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken).Token; + else + _requestCancelToken = _clearToken.Token; } - finally { _lock.Release(); } + finally { _tokenLock.Release(); } } public async Task SendAsync(RestRequest request) { - request.CancelToken = _cancelToken; - var bucket = GetOrCreateBucket(request.Options.BucketId); + request.CancelToken = _requestCancelToken; + var bucket = GetOrCreateBucket(request.BucketId); return await bucket.SendAsync(request).ConfigureAwait(false); } - public async Task SendAsync(WebSocketRequest request) + public async Task SendAsync(WebSocketRequest request) { - request.CancelToken = _cancelToken; - var bucket = GetOrCreateBucket(request.Options.BucketId); - return await bucket.SendAsync(request).ConfigureAwait(false); + //TODO: Re-impl websocket buckets + request.CancelToken = _requestCancelToken; + await request.SendAsync().ConfigureAwait(false); } - - private RequestQueueBucket GetOrCreateBucket(string id) + + internal async Task EnterGlobalAsync(int id, RestRequest request) + { + int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds); + if (millis > 0) + { + Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]"); + await Task.Delay(millis).ConfigureAwait(false); + } + } + internal void PauseGlobal(RateLimitInfo info, TimeSpan lag) { - return new RequestQueueBucket(this, id, null); + _waitUntil = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value + lag.TotalMilliseconds); } - public void DestroyBucket(string id) + private RequestBucket GetOrCreateBucket(string id) { - //Assume this object is locked - RequestQueueBucket bucket; - _buckets.TryRemove(id, out bucket); + return _buckets.GetOrAdd(id, x => new RequestBucket(this, x)); + } + internal async Task RaiseRateLimitTriggered(string bucketId, RateLimitInfo? info) + { + await RateLimitTriggered(bucketId, info).ConfigureAwait(false); } - public async Task ClearAsync() + private async Task RunCleanup() { - await _lock.WaitAsync().ConfigureAwait(false); try { - _clearToken?.Cancel(); - _clearToken = new CancellationTokenSource(); - if (_parentToken != null) - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken).Token; - else - _cancelToken = _clearToken.Token; + while (!_cancelToken.IsCancellationRequested) + { + var now = DateTimeOffset.UtcNow; + foreach (var bucket in _buckets.Select(x => x.Value)) + { + RequestBucket ignored; + if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) + _buckets.TryRemove(bucket.Id, out ignored); + } + await Task.Delay(60000, _cancelToken.Token); //Runs each minute + } } - finally { _lock.Release(); } + catch (OperationCanceledException) { } + catch (ObjectDisposedException) { } } - internal async Task RaiseRateLimitTriggered(string id, RequestQueueBucket bucket, int? millis) + public void Dispose() { - await RateLimitTriggered(id, bucket, millis).ConfigureAwait(false); + _cancelToken.Dispose(); } } } diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs index a94d0f05e..211a68eab 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs @@ -1,5 +1,7 @@ -#pragma warning disable CS4014 +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; +using System.Diagnostics; using System.IO; using System.Net; using System.Threading; @@ -7,145 +9,230 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - public class RequestQueueBucket + internal class RequestBucket { + private readonly object _lock; private readonly RequestQueue _queue; - private readonly SemaphoreSlim _semaphore; - private readonly object _pauseLock; - private int _pauseEndTick; - private TaskCompletionSource _resumeNotifier; + private int _semaphore; + private DateTimeOffset? _resetTick; - public string Id { get; } - public RequestQueueBucket Parent { get; } - public int WindowSeconds { get; } + public string Id { get; private set; } + public int WindowCount { get; private set; } + public DateTimeOffset LastAttemptAt { get; private set; } - public RequestQueueBucket(RequestQueue queue, string id, RequestQueueBucket parent = null) + public RequestBucket(RequestQueue queue, string id) { _queue = queue; Id = id; - _semaphore = new SemaphoreSlim(5, 5); - Parent = parent; - _pauseLock = new object(); - _resumeNotifier = new TaskCompletionSource(); - _resumeNotifier.SetResult(0); - } + _lock = new object(); - public async Task SendAsync(IQueuedRequest request) + if (queue.TokenType == TokenType.User) + WindowCount = ClientBucket.Get(Id).WindowCount; + else + WindowCount = 1; //Only allow one request until we get a header back + _semaphore = WindowCount; + _resetTick = null; + LastAttemptAt = DateTimeOffset.UtcNow; + } + + static int nextId = 0; + public async Task SendAsync(RestRequest request) { + int id = Interlocked.Increment(ref nextId); + Debug.WriteLine($"[{id}] Start"); + LastAttemptAt = DateTimeOffset.UtcNow; while (true) { - try + await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false); + await EnterAsync(id, request).ConfigureAwait(false); + + Debug.WriteLine($"[{id}] Sending..."); + var response = await request.SendAsync().ConfigureAwait(false); + TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]); + var info = new RateLimitInfo(response.Headers); + + if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) { - return await SendAsyncInternal(request).ConfigureAwait(false); + switch (response.StatusCode) + { + case (HttpStatusCode)429: + if (info.IsGlobal) + { + Debug.WriteLine($"[{id}] (!) 429 [Global]"); + _queue.PauseGlobal(info, lag); + } + else + { + Debug.WriteLine($"[{id}] (!) 429"); + Update(id, info, lag); + } + await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false); + continue; //Retry + case HttpStatusCode.BadGateway: //502 + Debug.WriteLine($"[{id}] (!) 502"); + continue; //Continue + default: + 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); + reason = json.Value("message"); + } + } + catch { } + } + throw new HttpException(response.StatusCode, reason); + } } - catch (HttpRateLimitException ex) + else { - //When a 429 occurs, we drop all our locks. - //This is generally safe though since 429s actually occuring should be very rare. - await _queue.RaiseRateLimitTriggered(Id, this, ex.RetryAfterMilliseconds).ConfigureAwait(false); - Pause(ex.RetryAfterMilliseconds); + Debug.WriteLine($"[{id}] Success"); + Update(id, info, lag); + Debug.WriteLine($"[{id}] Stop"); + return response.Stream; } } } - private async Task SendAsyncInternal(IQueuedRequest request) + + private async Task EnterAsync(int id, RestRequest request) { - var endTick = request.TimeoutTick; + int windowCount; + DateTimeOffset? resetAt; + bool isRateLimited = false; - //Wait until a spot is open in our bucket - if (_semaphore != null) - await EnterAsync(endTick).ConfigureAwait(false); - try + while (true) { - while (true) + if (DateTimeOffset.UtcNow > request.TimeoutAt || request.CancelToken.IsCancellationRequested) { - //Get our 429 state - Task notifier; - int resumeTime; - - lock (_pauseLock) - { - notifier = _resumeNotifier.Task; - resumeTime = _pauseEndTick; - } - - //Are we paused due to a 429? - if (!notifier.IsCompleted) - { - //If the 429 ends after the maximum time for this request, timeout immediately - if (endTick.HasValue && endTick.Value < resumeTime) - throw new TimeoutException(); + if (!isRateLimited) + throw new TimeoutException(); + else + throw new RateLimitedException(); + } - //Wait for the 429 to complete - await notifier.ConfigureAwait(false); - } + lock (_lock) + { + windowCount = WindowCount; + resetAt = _resetTick; + } - try + DateTimeOffset? timeoutAt = request.TimeoutAt; + if (windowCount > 0 && Interlocked.Decrement(ref _semaphore) < 0) + { + isRateLimited = true; + await _queue.RaiseRateLimitTriggered(Id, null).ConfigureAwait(false); + if (resetAt.HasValue) { - //If there's a parent bucket, pass this request to them - if (Parent != null) - return await Parent.SendAsyncInternal(request).ConfigureAwait(false); - - //We have all our semaphores, send the request - return await request.SendAsync().ConfigureAwait(false); + if (resetAt > timeoutAt) + throw new RateLimitedException(); + int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); + Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); + if (millis > 0) + await Task.Delay(millis, request.CancelToken).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.BadGateway) + else { - continue; + if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0) + throw new RateLimitedException(); + Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)"); + await Task.Delay(500, request.CancelToken).ConfigureAwait(false); } + continue; } - } - finally - { - //Make sure we put this entry back after WindowMilliseconds - if (_semaphore != null) - QueueExitAsync(); + else + Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)"); + break; } } - - private void Pause(int milliseconds) + + private void Update(int id, RateLimitInfo info, TimeSpan lag) { - lock (_pauseLock) + lock (_lock) { - //If we aren't already waiting on a 429's time, create a new notifier task - if (_resumeNotifier.Task.IsCompleted) + if (!info.Limit.HasValue && _queue.TokenType != TokenType.User) { - _resumeNotifier = new TaskCompletionSource(); - _pauseEndTick = unchecked(Environment.TickCount + milliseconds); - QueueResumeAsync(_resumeNotifier, milliseconds); + WindowCount = 0; + return; } - } - } - private async Task QueueResumeAsync(TaskCompletionSource resumeNotifier, int millis) - { - await Task.Delay(millis).ConfigureAwait(false); - resumeNotifier.TrySetResultAsync(0); - } - private async Task EnterAsync(int? endTick) - { - if (endTick.HasValue) - { - int millis = unchecked(endTick.Value - Environment.TickCount); - if (millis <= 0 || !await _semaphore.WaitAsync(millis).ConfigureAwait(false)) - throw new TimeoutException(); + bool hasQueuedReset = _resetTick != null; + if (info.Limit.HasValue && WindowCount != info.Limit.Value) + { + WindowCount = info.Limit.Value; + _semaphore = info.Remaining.Value; + Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount} "); + } + + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + DateTimeOffset resetTick; - if (!await _semaphore.WaitAsync(0).ConfigureAwait(false)) + //Using X-RateLimit-Remaining causes a race condition + /*if (info.Remaining.HasValue) + { + Debug.WriteLine($"[{id}] X-RateLimit-Remaining: " + info.Remaining.Value); + _semaphore = info.Remaining.Value; + }*/ + if (info.RetryAfter.HasValue) { - await _queue.RaiseRateLimitTriggered(Id, this, null).ConfigureAwait(false); + //RetryAfter is more accurate than Reset, where available + resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value); + Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); + } + else if (info.Reset.HasValue) + { + resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds); + int diff = (int)(resetTick - DateTimeOffset.UtcNow).TotalMilliseconds; + Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {lag.TotalMilliseconds} ms lag)"); + } + else if (_queue.TokenType == TokenType.User) + { + resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(Id).WindowSeconds); + Debug.WriteLine($"[{id}] Client Bucket: " + ClientBucket.Get(Id).WindowSeconds); + } - millis = unchecked(endTick.Value - Environment.TickCount); - if (millis <= 0 || !await _semaphore.WaitAsync(millis).ConfigureAwait(false)) - throw new TimeoutException(); + if (resetTick == null) + { + resetTick = DateTimeOffset.UtcNow.AddSeconds(1.0); //Forcibly reset in a second + Debug.WriteLine($"[{id}] Unknown Retry Time!"); + } + + if (!hasQueuedReset || resetTick > _resetTick) + { + _resetTick = resetTick; + LastAttemptAt = resetTick; //Make sure we dont destroy this until after its been reset + Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).TotalMilliseconds)} ms"); + + if (!hasQueuedReset) + { + var _ = QueueReset(id, (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds)); + } } } - else - await _semaphore.WaitAsync().ConfigureAwait(false); } - private async Task QueueExitAsync() + private async Task QueueReset(int id, int millis) { - await Task.Delay(WindowSeconds * 1000).ConfigureAwait(false); - _semaphore.Release(); + while (true) + { + if (millis > 0) + await Task.Delay(millis).ConfigureAwait(false); + lock (_lock) + { + millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds); + if (millis <= 0) //Make sure we havent gotten a more accurate reset time + { + Debug.WriteLine($"[{id}] * Reset *"); + _semaphore = WindowCount; + _resetTick = null; + return; + } + } + } } } -} +} \ No newline at end of file diff --git a/src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs deleted file mode 100644 index 492b3a77d..000000000 --- a/src/Discord.Net.Core/Net/Queue/Requests/IQueuedRequest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Discord.Net.Queue -{ - public interface IQueuedRequest - { - CancellationToken CancelToken { get; } - int? TimeoutTick { get; } - - Task SendAsync(); - } -} diff --git a/src/Discord.Net.Core/Net/Queue/Requests/IRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/IRequest.cs new file mode 100644 index 000000000..c8d861a11 --- /dev/null +++ b/src/Discord.Net.Core/Net/Queue/Requests/IRequest.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading; + +namespace Discord.Net.Queue +{ + public interface IRequest + { + CancellationToken CancelToken { get; } + DateTimeOffset? TimeoutAt { get; } + string BucketId { get; } + } +} diff --git a/src/Discord.Net.Core/Net/Queue/Requests/JsonRestRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/JsonRestRequest.cs index d715b790c..d328a3e26 100644 --- a/src/Discord.Net.Core/Net/Queue/Requests/JsonRestRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/Requests/JsonRestRequest.cs @@ -1,5 +1,4 @@ using Discord.Net.Rest; -using System.IO; using System.Threading.Tasks; namespace Discord.Net.Queue @@ -8,13 +7,13 @@ namespace Discord.Net.Queue { public string Json { get; } - public JsonRestRequest(IRestClient client, string method, string endpoint, string json, RequestOptions options) - : base(client, method, endpoint, options) + public JsonRestRequest(IRestClient client, string method, string endpoint, string bucket, string json, RequestOptions options) + : base(client, method, endpoint, bucket, options) { Json = json; } - public override async Task SendAsync() + public override async Task SendAsync() { return await Client.SendAsync(Method, Endpoint, Json, Options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs index 047e5ed02..e27bb92a0 100644 --- a/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/Requests/MultipartRestRequest.cs @@ -1,6 +1,5 @@ using Discord.Net.Rest; using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; namespace Discord.Net.Queue @@ -9,13 +8,13 @@ namespace Discord.Net.Queue { public IReadOnlyDictionary MultipartParams { get; } - public MultipartRestRequest(IRestClient client, string method, string endpoint, IReadOnlyDictionary multipartParams, RequestOptions options) - : base(client, method, endpoint, options) + public MultipartRestRequest(IRestClient client, string method, string endpoint, string bucket, IReadOnlyDictionary multipartParams, RequestOptions options) + : base(client, method, endpoint, bucket, options) { MultipartParams = multipartParams; } - public override async Task SendAsync() + public override async Task SendAsync() { return await Client.SendAsync(Method, Endpoint, MultipartParams, Options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Core/Net/Queue/Requests/RestRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/RestRequest.cs index 655a79567..8382003c8 100644 --- a/src/Discord.Net.Core/Net/Queue/Requests/RestRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/Requests/RestRequest.cs @@ -6,29 +6,31 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - public class RestRequest : IQueuedRequest + public class RestRequest : IRequest { public IRestClient Client { get; } public string Method { get; } public string Endpoint { get; } - public int? TimeoutTick { get; } + public string BucketId { get; } + public DateTimeOffset? TimeoutAt { get; } public TaskCompletionSource Promise { get; } public RequestOptions Options { get; } public CancellationToken CancelToken { get; internal set; } - public RestRequest(IRestClient client, string method, string endpoint, RequestOptions options) + public RestRequest(IRestClient client, string method, string endpoint, string bucketId, RequestOptions options) { Preconditions.NotNull(options, nameof(options)); Client = client; Method = method; Endpoint = endpoint; + BucketId = bucketId; Options = options; - TimeoutTick = options.Timeout.HasValue ? (int?)unchecked(Environment.TickCount + options.Timeout.Value) : null; + TimeoutAt = options.Timeout.HasValue ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) : (DateTimeOffset?)null; Promise = new TaskCompletionSource(); } - public virtual async Task SendAsync() + public virtual async Task SendAsync() { return await Client.SendAsync(Method, Endpoint, Options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Core/Net/Queue/Requests/WebSocketRequest.cs b/src/Discord.Net.Core/Net/Queue/Requests/WebSocketRequest.cs index 796517c85..08cdb192c 100644 --- a/src/Discord.Net.Core/Net/Queue/Requests/WebSocketRequest.cs +++ b/src/Discord.Net.Core/Net/Queue/Requests/WebSocketRequest.cs @@ -6,32 +6,33 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - public class WebSocketRequest : IQueuedRequest + public class WebSocketRequest : IRequest { public IWebSocketClient Client { get; } + public string BucketId { get; } public byte[] Data { get; } public bool IsText { get; } - public int? TimeoutTick { get; } + public DateTimeOffset? TimeoutAt { get; } public TaskCompletionSource Promise { get; } public RequestOptions Options { get; } public CancellationToken CancelToken { get; internal set; } - public WebSocketRequest(IWebSocketClient client, byte[] data, bool isText, RequestOptions options) + public WebSocketRequest(IWebSocketClient client, string bucketId, byte[] data, bool isText, RequestOptions options) { Preconditions.NotNull(options, nameof(options)); Client = client; + BucketId = bucketId; Data = data; IsText = isText; Options = options; - TimeoutTick = options.Timeout.HasValue ? (int?)unchecked(Environment.TickCount + options.Timeout.Value) : null; + TimeoutAt = options.Timeout.HasValue ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) : (DateTimeOffset?)null; Promise = new TaskCompletionSource(); } - public async Task SendAsync() + public async Task SendAsync() { await Client.SendAsync(Data, 0, Data.Length, IsText).ConfigureAwait(false); - return null; } } } diff --git a/src/Discord.Net.Core/Net/RateLimitException.cs b/src/Discord.Net.Core/Net/RateLimitException.cs deleted file mode 100644 index cb0ca7f28..000000000 --- a/src/Discord.Net.Core/Net/RateLimitException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Net; - -namespace Discord.Net -{ - public class HttpRateLimitException : HttpException - { - public string Id { get; } - public int RetryAfterMilliseconds { get; } - - public HttpRateLimitException(string bucketId, int retryAfterMilliseconds, string reason) - : base((HttpStatusCode)429, reason) - { - Id = bucketId; - RetryAfterMilliseconds = retryAfterMilliseconds; - } - } -} diff --git a/src/Discord.Net.Core/Net/RateLimitInfo.cs b/src/Discord.Net.Core/Net/RateLimitInfo.cs new file mode 100644 index 000000000..2c2faccf8 --- /dev/null +++ b/src/Discord.Net.Core/Net/RateLimitInfo.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Discord.Net +{ + public struct RateLimitInfo + { + public bool IsGlobal { get; } + public int? Limit { get; } + public int? Remaining { get; } + public int? RetryAfter { get; } + public DateTimeOffset? Reset { get; } + + internal RateLimitInfo(Dictionary headers) + { + string temp; + IsGlobal = headers.TryGetValue("X-RateLimit-Global", out temp) ? bool.Parse(temp) : false; + Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) ? int.Parse(temp) : (int?)null; + Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) ? int.Parse(temp) : (int?)null; + Reset = headers.TryGetValue("X-RateLimit-Reset", out temp) ? DateTimeOffset.FromUnixTimeSeconds(int.Parse(temp)) : (DateTimeOffset?)null; + RetryAfter = headers.TryGetValue("Retry-After", out temp) ? int.Parse(temp) : (int?)null; + } + } +} diff --git a/src/Discord.Net.Core/Net/RateLimitedException.cs b/src/Discord.Net.Core/Net/RateLimitedException.cs new file mode 100644 index 000000000..e8572f911 --- /dev/null +++ b/src/Discord.Net.Core/Net/RateLimitedException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Discord.Net +{ + public class RateLimitedException : TimeoutException + { + public RateLimitedException() + : base("You are being rate limited.") + { + } + } +} diff --git a/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs b/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs index b06df37b8..02c356efd 100644 --- a/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; @@ -67,13 +66,13 @@ namespace Discord.Net.Rest _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; } - public async Task SendAsync(string method, string endpoint, RequestOptions options) + public async Task SendAsync(string method, string endpoint, RequestOptions options) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) return await SendInternalAsync(restRequest, options).ConfigureAwait(false); } - public async Task SendAsync(string method, string endpoint, string json, RequestOptions options) + public async Task SendAsync(string method, string endpoint, string json, RequestOptions options) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) @@ -82,7 +81,7 @@ namespace Discord.Net.Rest return await SendInternalAsync(restRequest, options).ConfigureAwait(false); } } - public async Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, RequestOptions options) + public async Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, RequestOptions options) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) @@ -114,50 +113,17 @@ namespace Discord.Net.Rest } } - private async Task SendInternalAsync(HttpRequestMessage request, RequestOptions options) + private async Task SendInternalAsync(HttpRequestMessage request, RequestOptions options) { while (true) { var cancelToken = _cancelToken; //It's okay if another thread changes this, causes a retry to abort HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); + + var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault()); + var stream = !options.HeaderOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null; - int statusCode = (int)response.StatusCode; - if (statusCode < 200 || statusCode >= 300) //2xx = Success - { - string reason = null; - JToken content = null; - if (response.Content.Headers.GetValues("content-type").FirstOrDefault() == "application/json") - { - try - { - using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) - using (var reader = new StreamReader(stream)) - using (var json = new JsonTextReader(reader)) - { - content = _errorDeserializer.Deserialize(json); - reason = content.Value("message"); - if (reason == null) //Occasionally an error message is given under a different key because reasons - reason = content.ToString(Formatting.None); - } - } - catch { } //Might have been HTML Should we check for content-type? - } - - if (statusCode == 429 && content != null) - { - //TODO: Include bucket info - string bucketId = content.Value("bucket"); - int retryAfterMillis = content.Value("retry_after"); - throw new HttpRateLimitException(bucketId, retryAfterMillis, reason); - } - else - throw new HttpException(response.StatusCode, reason); - } - - if (options.HeaderOnly) - return null; - else - return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return new RestResponse(response.StatusCode, headers, stream); } } diff --git a/src/Discord.Net.Core/Net/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs index aa53bea5b..16cfbe62d 100644 --- a/src/Discord.Net.Core/Net/Rest/IRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/IRestClient.cs @@ -1,5 +1,5 @@ +using Discord.Net.Queue; using System.Collections.Generic; -using System.IO; using System.Threading; using System.Threading.Tasks; @@ -10,8 +10,8 @@ namespace Discord.Net.Rest void SetHeader(string key, string value); void SetCancelToken(CancellationToken cancelToken); - Task SendAsync(string method, string endpoint, RequestOptions options); - Task SendAsync(string method, string endpoint, string json, RequestOptions options); - Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, RequestOptions options); + Task SendAsync(string method, string endpoint, RequestOptions options); + Task SendAsync(string method, string endpoint, string json, RequestOptions options); + Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, RequestOptions options); } } diff --git a/src/Discord.Net.Core/Net/Rest/RestResponse.cs b/src/Discord.Net.Core/Net/Rest/RestResponse.cs new file mode 100644 index 000000000..412ff4dce --- /dev/null +++ b/src/Discord.Net.Core/Net/Rest/RestResponse.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.IO; +using System.Net; + +namespace Discord.Net.Rest +{ + public struct RestResponse + { + public HttpStatusCode StatusCode { get; } + public Dictionary Headers { get; } + public Stream Stream { get; } + + public RestResponse(HttpStatusCode statusCode, Dictionary headers, Stream stream) + { + StatusCode = statusCode; + Headers = headers; + Stream = stream; + } + } +} diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 9c9986b29..1d362fad1 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -6,7 +6,6 @@ /// 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 string BucketId { get; set; } public bool HeaderOnly { get; internal set; } internal bool IgnoreState { get; set; } diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 62dcfd055..4ed019dfb 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -40,11 +40,12 @@ namespace Discord.Rest _queueLogger = LogManager.CreateLogger("Queue"); _isFirstLogin = true; - ApiClient.RequestQueue.RateLimitTriggered += async (id, bucket, millis) => + ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => { - await _queueLogger.WarningAsync($"Rate limit triggered (id = \"{id ?? "null"}\")").ConfigureAwait(false); - if (bucket == null && id != null) - await _queueLogger.WarningAsync($"Unknown rate limit bucket \"{id ?? "null"}\"").ConfigureAwait(false); + if (info == null) + await _queueLogger.WarningAsync($"Preemptive Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); + else + await _queueLogger.WarningAsync($"Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); }; ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); } diff --git a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs index 67ead6f83..050783f28 100644 --- a/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/API/DiscordRpcApiClient.cs @@ -233,7 +233,7 @@ namespace Discord.API var requestTracker = new RpcRequest(options); _requests[guid] = requestTracker; - await _requestQueue.SendAsync(new WebSocketRequest(_webSocketClient, bytes, true, options)).ConfigureAwait(false); + await _requestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, bytes, true, options)).ConfigureAwait(false); await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); return await requestTracker.Promise.Task.ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs index b1bb61eb2..f0dd5f852 100644 --- a/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/API/DiscordSocketApiClient.cs @@ -167,7 +167,7 @@ namespace Discord.API payload = new SocketFrame { Operation = (int)opCode, Payload = payload }; if (payload != null) bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); - await RequestQueue.SendAsync(new WebSocketRequest(_gatewayClient, bytes, true, options)).ConfigureAwait(false); + await RequestQueue.SendAsync(new WebSocketRequest(_gatewayClient, null, bytes, true, options)).ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); } @@ -175,7 +175,7 @@ namespace Discord.API public async Task GetGatewayAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", "gateway", options: options).ConfigureAwait(false); + return await SendAsync("GET", () => "gateway", new BucketIds(), options: options).ConfigureAwait(false); } public async Task SendIdentifyAsync(int largeThreshold = 100, bool useCompression = true, int shardID = 0, int totalShards = 1, RequestOptions options = null) { From 1057ee012a8efbd706a0cf1a32895b72a5bad46b Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 11 Oct 2016 23:45:27 -0300 Subject: [PATCH 102/102] Made a few methods static --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index e2fcbf9af..93dbabef4 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -1065,16 +1065,16 @@ namespace Discord.API } } - private string GetEndpoint(Expression> endpointExpr) + private static string GetEndpoint(Expression> endpointExpr) { return endpointExpr.Compile()(); } - private string GetBucketId(BucketIds ids, Expression> endpointExpr, string callingMethod) + private static string GetBucketId(BucketIds ids, Expression> endpointExpr, string callingMethod) { return _bucketIdGenerators.GetOrAdd(callingMethod, x => CreateBucketId(endpointExpr))(ids); } - private Func CreateBucketId(Expression> endpoint) + private static Func CreateBucketId(Expression> endpoint) { try {