Browse Source

Merge branch 'dev' of https://github.com/RogueException/Discord.Net into feature/channel-categories

pull/907/head
mrspits4ever 7 years ago
parent
commit
71142c3108
92 changed files with 1967 additions and 1148 deletions
  1. +7
    -1
      Discord.Net.sln
  2. +2
    -2
      Discord.Net.targets
  3. +2
    -2
      README.md
  4. +1
    -1
      appveyor.yml
  5. +134
    -114
      docs/guides/commands/commands.md
  6. +23
    -20
      docs/guides/commands/samples/command_handler.cs
  7. +16
    -16
      docs/guides/commands/samples/dependency_map_setup.cs
  8. +1
    -1
      docs/guides/commands/samples/empty-module.cs
  9. +2
    -2
      docs/guides/commands/samples/groups.cs
  10. +29
    -30
      docs/guides/commands/samples/module.cs
  11. +7
    -4
      docs/guides/concepts/entities.md
  12. +10
    -10
      docs/guides/concepts/events.md
  13. +7
    -2
      docs/guides/concepts/logging.md
  14. +65
    -60
      docs/guides/getting_started/installing.md
  15. +72
    -62
      docs/guides/getting_started/intro.md
  16. +2
    -2
      docs/guides/getting_started/samples/intro/message.cs
  17. +20
    -13
      docs/guides/getting_started/samples/intro/structure.cs
  18. +1
    -1
      docs/guides/getting_started/samples/project.csproj
  19. +17
    -15
      docs/guides/getting_started/terminology.md
  20. +4
    -5
      docs/guides/voice/samples/audio_create_ffmpeg.cs
  21. +7
    -5
      docs/guides/voice/samples/audio_ffmpeg.cs
  22. +1
    -1
      docs/guides/voice/samples/joining_audio.cs
  23. +4
    -4
      docs/guides/voice/sending-voice.md
  24. +1
    -1
      src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs
  25. +2
    -2
      src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs
  26. +3
    -5
      src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs
  27. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs
  28. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs
  29. +3
    -2
      src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs
  30. +3
    -5
      src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs
  31. +3
    -3
      src/Discord.Net.Commands/CommandParser.cs
  32. +5
    -4
      src/Discord.Net.Commands/CommandServiceConfig.cs
  33. +10
    -7
      src/Discord.Net.Commands/Info/CommandInfo.cs
  34. +3
    -3
      src/Discord.Net.Commands/Info/ParameterInfo.cs
  35. +1
    -1
      src/Discord.Net.Commands/Readers/ChannelTypeReader.cs
  36. +1
    -1
      src/Discord.Net.Commands/Readers/EnumTypeReader.cs
  37. +1
    -1
      src/Discord.Net.Commands/Readers/MessageTypeReader.cs
  38. +2
    -2
      src/Discord.Net.Commands/Readers/NullableTypeReader.cs
  39. +1
    -1
      src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs
  40. +1
    -1
      src/Discord.Net.Commands/Readers/RoleTypeReader.cs
  41. +1
    -1
      src/Discord.Net.Commands/Readers/TypeReader.cs
  42. +1
    -1
      src/Discord.Net.Commands/Readers/UserTypeReader.cs
  43. +1
    -1
      src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs
  44. +0
    -4
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  45. +6
    -0
      src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
  46. +10
    -0
      src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs
  47. +9
    -0
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  48. +29
    -33
      src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs
  49. +15
    -15
      src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
  50. +37
    -33
      src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
  51. +40
    -28
      src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
  52. +19
    -18
      src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs
  53. +4
    -1
      src/Discord.Net.Core/TokenType.cs
  54. +1
    -1
      src/Discord.Net.Core/Utils/Optional.cs
  55. +33
    -33
      src/Discord.Net.Core/Utils/Permissions.cs
  56. +2
    -1
      src/Discord.Net.Core/Utils/Preconditions.cs
  57. +16
    -0
      src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs
  58. +14
    -0
      src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs
  59. +8
    -4
      src/Discord.Net.Rest/BaseDiscordClient.cs
  60. +45
    -0
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  61. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  62. +2
    -2
      src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
  63. +0
    -5
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  64. +0
    -5
      src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
  65. +5
    -5
      src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
  66. +0
    -5
      src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs
  67. +41
    -0
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  68. +10
    -0
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  69. +35
    -0
      src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs
  70. +2
    -2
      src/Discord.Net.Rest/Net/DefaultRestClient.cs
  71. +14
    -9
      src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs
  72. +0
    -5
      src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs
  73. +0
    -5
      src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs
  74. +1
    -1
      src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs
  75. +0
    -2
      src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
  76. +193
    -0
      src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
  77. +93
    -0
      src/Discord.Net.WebSocket/BaseSocketClient.cs
  78. +22
    -183
      src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs
  79. +29
    -39
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  80. +35
    -8
      src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
  81. +5
    -186
      src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs
  82. +29
    -52
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  83. +0
    -5
      src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs
  84. +0
    -5
      src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs
  85. +3
    -3
      src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
  86. +12
    -1
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  87. +1
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
  88. +5
    -2
      src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs
  89. +15
    -9
      src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs
  90. +19
    -19
      src/Discord.Net/Discord.Net.nuspec
  91. +324
    -0
      test/Discord.Net.Tests/Tests.ChannelPermissions.cs
  92. +304
    -0
      test/Discord.Net.Tests/Tests.GuildPermissions.cs

+ 7
- 1
Discord.Net.sln View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26228.4
VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}"
EndProject EndProject
@@ -142,4 +142,10 @@ Global
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012}
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495}
EndGlobalSection
GlobalSection(CodealikeProperties) = postSolution
SolutionGuid = a45217b4-a401-4dbf-8654-34d2ec034cd9
EndGlobalSection
EndGlobal EndGlobal

+ 2
- 2
Discord.Net.targets View File

@@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<VersionPrefix>2.0.0-alpha</VersionPrefix>
<VersionSuffix></VersionSuffix>
<VersionPrefix>2.0.0</VersionPrefix>
<VersionSuffix>beta</VersionSuffix>
<Authors>RogueException</Authors> <Authors>RogueException</Authors>
<PackageTags>discord;discordapp</PackageTags> <PackageTags>discord;discordapp</PackageTags>
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>


+ 2
- 2
README.md View File

@@ -2,11 +2,11 @@
[![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net) [![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net)
[![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net) [![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net)
[![Build status](https://ci.appveyor.com/api/projects/status/5sb7n8a09w9clute/branch/dev?svg=true)](https://ci.appveyor.com/project/RogueException/discord-net/branch/dev) [![Build status](https://ci.appveyor.com/api/projects/status/5sb7n8a09w9clute/branch/dev?svg=true)](https://ci.appveyor.com/project/RogueException/discord-net/branch/dev)
[![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/0SBTUU1wZTVjAMPx)
[![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/jkrBmQR)


An unofficial .NET API Wrapper for the Discord client (http://discordapp.com). An unofficial .NET API Wrapper for the Discord client (http://discordapp.com).


Check out the [documentation](https://discord.foxbot.me/docs/) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx).
Check out the [documentation](https://discord.foxbot.me/docs/) or join the [Discord API Chat](https://discord.gg/jkrBmQR).


## Installation ## Installation
### Stable (NuGet) ### Stable (NuGet)


+ 1
- 1
appveyor.yml View File

@@ -34,7 +34,7 @@ after_build:
if ($Env:APPVEYOR_REPO_TAG -eq "true") { if ($Env:APPVEYOR_REPO_TAG -eq "true") {
nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix=""
} else { } else {
nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-build-$Env:BUILD"
nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$Env:BUILD"
} }
- ps: Get-ChildItem artifacts\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } - ps: Get-ChildItem artifacts\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }




+ 134
- 114
docs/guides/commands/commands.md View File

@@ -1,24 +1,20 @@
# The Command Service # The Command Service


>[!WARNING]
>This article is out of date, and has not been rewritten yet.
Information is not guaranteed to be accurate.

[Discord.Commands](xref:Discord.Commands) provides an Attribute-based [Discord.Commands](xref:Discord.Commands) provides an Attribute-based
Command Parser.
command parser.


## Setup ## Setup


To use Commands, you must create a [Commands Service] and a
Command Handler.
To use Commands, you must create a [Command Service] and a Command
Handler.


Included below is a very bare-bones Command Handler. You can extend
your Command Handler as much as you like, however the below is the
bare minimum.
Included below is a very barebone Command Handler. You can extend your
Command Handler as much as you like; however, the below is the bare
minimum.


The CommandService optionally will accept a [CommandServiceConfig],
The `CommandService` will optionally accept a [CommandServiceConfig],
which _does_ set a few default values for you. It is recommended to which _does_ set a few default values for you. It is recommended to
look over the properties in [CommandServiceConfig], and their default
look over the properties in [CommandServiceConfig] and their default
values. values.


[!code-csharp[Command Handler](samples/command_handler.cs)] [!code-csharp[Command Handler](samples/command_handler.cs)]
@@ -28,32 +24,32 @@ values.


## With Attributes ## With Attributes


In 1.0, Commands can be defined ahead of time, with attributes, or
at runtime, with builders.
In 1.0, Commands can be defined ahead of time with attributes, or at
runtime with builders.


For most bots, ahead-of-time commands should be all you need, and this
is the recommended method of defining commands.
For most bots, ahead-of-time Commands should be all you need, and this
is the recommended method of defining Commands.


### Modules ### Modules


The first step to creating commands is to create a _module_.
The first step to creating Commands is to create a _module_.


Modules are an organizational pattern that allow you to write your
commands in different classes, and have them automatically loaded.
A Module is an organizational pattern that allows you to write your
Commands in different classes and have them automatically loaded.


Discord.Net's implementation of Modules is influenced heavily from Discord.Net's implementation of Modules is influenced heavily from
ASP.Net Core's Controller pattern. This means that the lifetime of a
module instance is only as long as the command being invoked.
ASP.NET Core's Controller pattern. This means that the lifetime of a
module instance is only as long as the Command is being invoked.


**Avoid using long-running code** in your modules wherever possible. **Avoid using long-running code** in your modules wherever possible.
You should **not** be implementing very much logic into your modules;
outsource to a service for that.
You should **not** be implementing very much logic into your modules,
instead, outsource to a service for that.


If you are unfamiliar with Inversion of Control, it is recommended to If you are unfamiliar with Inversion of Control, it is recommended to
read the MSDN article on [IoC] and [Dependency Injection]. read the MSDN article on [IoC] and [Dependency Injection].


To begin, create a new class somewhere in your project, and
inherit the class from [ModuleBase]. This class **must** be `public`.
To begin, create a new class somewhere in your project and inherit the
class from [ModuleBase]. This class **must** be `public`.


>[!NOTE] >[!NOTE]
>[ModuleBase] is an _abstract_ class, meaning that you may extend it >[ModuleBase] is an _abstract_ class, meaning that you may extend it
@@ -61,6 +57,7 @@ inherit the class from [ModuleBase]. This class **must** be `public`.
>extension of ModuleBase. >extension of ModuleBase.


By now, your module should look like this: By now, your module should look like this:

[!code-csharp[Empty Module](samples/empty-module.cs)] [!code-csharp[Empty Module](samples/empty-module.cs)]


[IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx [IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx
@@ -69,72 +66,75 @@ By now, your module should look like this:


### Adding Commands ### Adding Commands


The next step to creating commands, is actually creating commands.
The next step to creating Commands is actually creating the Commands.


To create a command, add a method to your module of type `Task`.
Typically, you will want to mark this method as `async`, although it is
not required.
To create a Command, add a method to your module of type `Task`.
Typically, you will want to mark this method as `async`, although it
is not required.


Adding parameters to a command is done by adding parameters to the
Adding parameters to a Command is done by adding parameters to the
parent Task. parent Task.


For example, to take an integer as an argument, add `int arg`. To take
a user as an argument, add `IUser user`. In 1.0, a command can accept
nearly any type of argument; a full list of types that are parsed by
default can be found in the below section on _Type Readers_.
For example, to take an integer as an argument from the user, add `int
arg`; to take a user as an argument from the user, add `IUser user`.
In 1.0, a Command can accept nearly any type of argument; a full list
of types that are parsed by default can be found in the below section
on _Type Readers_.


Parameters, by default, are always required. To make a parameter Parameters, by default, are always required. To make a parameter
optional, give it a default value. To accept a comma-separated list, optional, give it a default value. To accept a comma-separated list,
set the parameter to `params Type[]`. set the parameter to `params Type[]`.


Should a parameter include spaces, it **must** be wrapped in quotes. Should a parameter include spaces, it **must** be wrapped in quotes.
For example, for a command with a parameter `string food`, you would
For example, for a Command with a parameter `string food`, you would
execute it with `!favoritefood "Key Lime Pie"`. execute it with `!favoritefood "Key Lime Pie"`.


If you would like a parameter to parse until the end of a command,
If you would like a parameter to parse until the end of a Command,
flag the parameter with the [RemainderAttribute]. This will allow a flag the parameter with the [RemainderAttribute]. This will allow a
user to invoke a command without wrapping a parameter in quotes.
user to invoke a Command without wrapping a parameter in quotes.


Finally, flag your command with the [CommandAttribute]. (You must
specify a name for this command, except for when it is part of a
module group - see below).
Finally, flag your Command with the [CommandAttribute] (you must
specify a name for this Command, except for when it is part of a
Module Group - see below).


[RemainderAttribute]: xref:Discord.Commands.RemainderAttribute [RemainderAttribute]: xref:Discord.Commands.RemainderAttribute
[CommandAttribute]: xref:Discord.Commands.CommandAttribute [CommandAttribute]: xref:Discord.Commands.CommandAttribute


### Command Overloads ### Command Overloads


You may add overloads of your commands, and the command parser will
You may add overloads to your Commands, and the Command parser will
automatically pick up on it. automatically pick up on it.


If, for whatever reason, you have too commands which are ambiguous to
If for whatever reason, you have two Commands which are ambiguous to
each other, you may use the @Discord.Commands.PriorityAttribute to each other, you may use the @Discord.Commands.PriorityAttribute to
specify which should be tested before the other. specify which should be tested before the other.


Priority's are sorted in ascending order; the higher priority will be
called first.
The `Priority` attributes are sorted in ascending order; the higher
priority will be called first.


### CommandContext
### Command Context


Every command can access the execution context through the [Context]
property on [ModuleBase]. CommandContext allows you to access the
message, channel, guild, and user that the command was invoked from,
as well as the underlying discord client the command was invoked from.
Every Command can access the execution context through the [Context]
property on [ModuleBase]. `ICommandContext` allows you to access the
message, channel, guild, and user that the Command was invoked from,
as well as the underlying Discord client that the Command was invoked
from.


Different types of Contexts may be specified using the generic variant Different types of Contexts may be specified using the generic variant
of [ModuleBase]. When using a [SocketCommandContext], for example,
the properties on this context will already be Socket entities. You
of [ModuleBase]. When using a [SocketCommandContext], for example, the
properties on this context will already be Socket entities, so you
will not need to cast them. will not need to cast them.


To reply to messages, you may also invoke [ReplyAsync], instead of To reply to messages, you may also invoke [ReplyAsync], instead of
accessing the channel through the [Context] and sending a message. accessing the channel through the [Context] and sending a message.


> [!WARNING]
>Contexts should **NOT** be mixed! You cannot have one module that
>uses `CommandContext` and another that uses `SocketCommandContext`.

[Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context [Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context
[SocketCommandContext]: xref:Discord.Commands.SocketCommandContext [SocketCommandContext]: xref:Discord.Commands.SocketCommandContext

>![WARNING]
>Contexts should **NOT** be mixed! You cannot have one module that
>uses CommandContext, and another that uses SocketCommandContext.
[ReplyAsync]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_ReplyAsync_System_String_System_Boolean_Discord_Embed_Discord_RequestOptions_


### Example Module ### Example Module


@@ -144,50 +144,50 @@ At this point, your module should look comparable to this example:
#### Loading Modules Automatically #### Loading Modules Automatically


The Command Service can automatically discover all classes in an The Command Service can automatically discover all classes in an
Assembly that inherit [ModuleBase], and load them.
Assembly that inherit [ModuleBase] and load them.


To opt a module out of auto-loading, flag it with To opt a module out of auto-loading, flag it with
[DontAutoLoadAttribute]
[DontAutoLoadAttribute].


Invoke [CommandService.AddModulesAsync] to discover modules and Invoke [CommandService.AddModulesAsync] to discover modules and
install them. install them.


[DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute [DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute
[CommandService.AddModulesAsync]: xref:Discord_Commands_CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_
[CommandService.AddModulesAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_


#### Loading Modules Manually #### Loading Modules Manually


To manually load a module, invoke [CommandService.AddModuleAsync],
by passing in the generic type of your module, and optionally
a dependency map.
To manually load a module, invoke [CommandService.AddModuleAsync] by
passing in the generic type of your module and optionally, a
dependency map.


[CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1 [CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1


### Module Constructors ### Module Constructors


Modules are constructed using Dependency Injection. Any parameters Modules are constructed using Dependency Injection. Any parameters
that are placed in the constructor must be injected into an
@System.IServiceProvider. Alternatively, you may accept an
IServiceProvider as an argument and extract services yourself.
that are placed in the Module's constructor must be injected into an
@System.IServiceProvider first. Alternatively, you may accept an
`IServiceProvider` as an argument and extract services yourself.


### Module Properties ### Module Properties


Modules with public settable properties will have them injected after module
construction.
Modules with `public` settable properties will have the dependencies
injected after the construction of the Module.


### Module Groups ### Module Groups


Module Groups allow you to create a module where commands are prefixed.
To create a group, flag a module with the
@Discord.Commands.GroupAttribute
Module Groups allow you to create a module where Commands are
prefixed. To create a group, flag a module with the
@Discord.Commands.GroupAttribute.


Module groups also allow you to create **nameless commands**, where the
[CommandAttribute] is configured with no name. In this case, the
command will inherit the name of the group it belongs to.
Module groups also allow you to create **nameless Commands**, where
the [CommandAttribute] is configured with no name. In this case, the
Command will inherit the name of the group it belongs to.


### Submodules ### Submodules


Submodules are modules that reside within another module. Typically,
Submodules are Modules that reside within another one. Typically,
submodules are used to create nested groups (although not required to submodules are used to create nested groups (although not required to
create nested groups). create nested groups).


@@ -199,54 +199,62 @@ create nested groups).


## Dependency Injection ## Dependency Injection


The commands service is bundled with a very barebones Dependency
Injection service for your convienence. It is recommended that
you use DI when writing your modules.
The Command Service is bundled with a very barebone Dependency
Injection service for your convenience. It is recommended that you use
DI when writing your modules.


### Setup ### Setup


First, you need to create an @System.IServiceProvider
You may create your own IServiceProvider if you wish.
First, you need to create an @System.IServiceProvider; you may create
your own one if you wish.


Next, add the dependencies your modules will use to the map.
Next, add the dependencies that your modules will use to the map.


Finally, pass the map into the `LoadAssembly` method.
Your modules will automatically be loaded with this dependency map.
Finally, pass the map into the `LoadAssembly` method. Your modules
will be automatically loaded with this dependency map.


[!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)]


### Usage in Modules ### Usage in Modules


In the constructor of your module, any parameters will be filled in by
the @System.IServiceProvider you pass into `LoadAssembly`.
In the constructor of your Module, any parameters will be filled in by
the @System.IServiceProvider that you've passed into `LoadAssembly`.


Any publicly settable properties will also be filled in the same manner.
Any publicly settable properties will also be filled in the same
manner.


>[!NOTE] >[!NOTE]
> Annotating a property with the [DontInject] attribute will prevent it from
being injected.
> Annotating a property with a [DontInjectAttribute] attribute will prevent the
property from being injected.


>[!NOTE] >[!NOTE]
>If you accept `CommandService` or `IServiceProvider` as a parameter in
your constructor or as an injectable property, these entries will be filled
by the CommandService the module was loaded from, and the ServiceProvider passed
into it, respectively.
>If you accept `CommandService` or `IServiceProvider` as a parameter
in your constructor or as an injectable property, these entries will
be filled by the `CommandService` that the Module is loaded from and
the `ServiceProvider` that is passed into it respectively.


[!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] [!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)]


[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute

# Preconditions # Preconditions


Preconditions serve as a permissions system for your commands. Keep in
mind, however, that they are not limited to _just_ permissions, and
can be as complex as you want them to be.
Precondition serve as a permissions system for your Commands. Keep in
mind, however, that they are not limited to _just_ permissions and can
be as complex as you want them to be.


>[!NOTE] >[!NOTE]
>Preconditions can be applied to Modules, Groups, or Commands.
>There are two types of Preconditions.
[PreconditionAttribute] can be applied to Modules, Groups, or Commands;
[ParameterPreconditionAttribute] can be applied to Parameters.

[PreconditionAttribute]: xref:Discord.Commands.PreconditionAttribute
[ParameterPreconditionAttribute]: xref:Discord.Commands.ParameterPreconditionAttribute


## Bundled Preconditions ## Bundled Preconditions


Commands ships with four bundled preconditions; you may view their
usages on their API page.
Commands ship with four bundled Preconditions; you may view their
usages on their respective API pages.


- @Discord.Commands.RequireContextAttribute - @Discord.Commands.RequireContextAttribute
- @Discord.Commands.RequireOwnerAttribute - @Discord.Commands.RequireOwnerAttribute
@@ -255,21 +263,23 @@ usages on their API page.


## Custom Preconditions ## Custom Preconditions


To write your own preconditions, create a new class that inherits from
@Discord.Commands.PreconditionAttribute
To write your own Precondition, create a new class that inherits from
either [PreconditionAttribute] or [ParameterPreconditionAttribute]
depending on your use.


In order for your precondition to function, you will need to override
[CheckPermissions].
In order for your Precondition to function, you will need to override
the [CheckPermissions] method.


Your IDE should provide an option to fill this in for you. Your IDE should provide an option to fill this in for you.


Return [PreconditionResult.FromSuccess] if the context met the
required parameters, otherwise return [PreconditionResult.FromError],
optionally including an error message.
If the context meets the required parameters, return
[PreconditionResult.FromSuccess], otherwise return
[PreconditionResult.FromError] and include an error message if
necessary.


[!code-csharp[Custom Precondition](samples/require_owner.cs)] [!code-csharp[Custom Precondition](samples/require_owner.cs)]


[CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_CommandContext_Discord_Commands_CommandInfo_Discord_Commands_IDependencyMap_
[CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_ICommandContext_Discord_Commands_CommandInfo_IServiceProvider_
[PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess [PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess
[PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_ [PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_


@@ -296,22 +306,28 @@ By default, the following Types are supported arguments:


### Creating a Type Readers ### Creating a Type Readers


To create a TypeReader, create a new class that imports @Discord and
@Discord.Commands. Ensure your class inherits from @Discord.Commands.TypeReader
To create a `TypeReader`, create a new class that imports @Discord and
@Discord.Commands and ensure the class inherits from
@Discord.Commands.TypeReader.


Next, satisfy the `TypeReader` class by overriding [Read].
Next, satisfy the `TypeReader` class by overriding the [Read] method.


>[!NOTE] >[!NOTE]
>In many cases, Visual Studio can fill this in for you, using the >In many cases, Visual Studio can fill this in for you, using the
>"Implement Abstract Class" IntelliSense hint. >"Implement Abstract Class" IntelliSense hint.


Inside this task, add whatever logic you need to parse the input string.
Inside this task, add whatever logic you need to parse the input
string.


Finally, return a `TypeReaderResult`. If you were able to successfully
parse the input, return `TypeReaderResult.FromSuccess(parsedInput)`.
Otherwise, return `TypeReaderResult.FromError`.
If you are able to successfully parse the input, return
[TypeReaderResult.FromSuccess] with the parsed input, otherwise return
[TypeReaderResult.FromError] and include an error message if
necessary.


[Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_CommandContext_System_String_
[TypeReaderResult]: xref:Discord.Commands.TypeReaderResult
[TypeReaderResult.FromSuccess]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromSuccess_Discord_Commands_TypeReaderValue_
[TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromError_Discord_Commands_CommandError_System_String_
[Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_ICommandContext_System_String_IServiceProvider_


#### Sample #### Sample


@@ -319,5 +335,9 @@ Otherwise, return `TypeReaderResult.FromError`.


### Installing TypeReaders ### Installing TypeReaders


TypeReaders are not automatically discovered by the Command Service,
and must be explicitly added. To install a TypeReader, invoke [CommandService.AddTypeReader](xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_).
TypeReaders are not automatically discovered by the Command Service
and must be explicitly added.

To install a TypeReader, invoke [CommandService.AddTypeReader].

[CommandService.AddTypeReader]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_

+ 23
- 20
docs/guides/commands/samples/command_handler.cs View File

@@ -8,39 +8,42 @@ using Microsoft.Extensions.DependencyInjection;


public class Program public class Program
{ {
private CommandService commands;
private DiscordSocketClient client;
private IServiceProvider services;
private CommandService _commands;
private DiscordSocketClient _client;
private IServiceProvider _services;


static void Main(string[] args) => new Program().Start().GetAwaiter().GetResult();
private static void Main(string[] args) => new Program().StartAsync().GetAwaiter().GetResult();


public async Task Start()
public async Task StartAsync()
{ {
client = new DiscordSocketClient();
commands = new CommandService();
_client = new DiscordSocketClient();
_commands = new CommandService();


// Avoid hard coding your token. Use an external source instead in your code.
string token = "bot token here"; string token = "bot token here";


services = new ServiceCollection()
.BuildServiceProvider();
_services = new ServiceCollection()
.AddSingleton(_client)
.AddSingleton(_commands)
.BuildServiceProvider();


await InstallCommands();
await InstallCommandsAsync();


await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();


await Task.Delay(-1); await Task.Delay(-1);
} }


public async Task InstallCommands()
public async Task InstallCommandsAsync()
{ {
// Hook the MessageReceived Event into our Command Handler // Hook the MessageReceived Event into our Command Handler
client.MessageReceived += HandleCommand;
_client.MessageReceived += HandleCommandAsync;
// Discover all of the commands in this assembly and load them. // Discover all of the commands in this assembly and load them.
await commands.AddModulesAsync(Assembly.GetEntryAssembly());
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
} }


public async Task HandleCommand(SocketMessage messageParam)
private async Task HandleCommandAsync(SocketMessage messageParam)
{ {
// Don't process the command if it was a System Message // Don't process the command if it was a System Message
var message = messageParam as SocketUserMessage; var message = messageParam as SocketUserMessage;
@@ -48,13 +51,13 @@ public class Program
// Create a number to track where the prefix ends and the command begins // Create a number to track where the prefix ends and the command begins
int argPos = 0; int argPos = 0;
// Determine if the message is a command, based on if it starts with '!' or a mention prefix // Determine if the message is a command, based on if it starts with '!' or a mention prefix
if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(client.CurrentUser, ref argPos))) return;
if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return;
// Create a Command Context // Create a Command Context
var context = new CommandContext(client, message);
var context = new SocketCommandContext(_client, message);
// Execute the command. (result does not indicate a return value, // Execute the command. (result does not indicate a return value,
// rather an object stating if the command executed successfully) // rather an object stating if the command executed successfully)
var result = await commands.ExecuteAsync(context, argPos, service);
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess) if (!result.IsSuccess)
await context.Channel.SendMessageAsync(result.ErrorReason); await context.Channel.SendMessageAsync(result.ErrorReason);
} }
}
}

+ 16
- 16
docs/guides/commands/samples/dependency_map_setup.cs View File

@@ -1,18 +1,18 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using foxboat.Services;
private IServiceProvider _services;
private CommandService _commands;


public class Commands
public async Task InstallAsync(DiscordSocketClient client)
{ {
public async Task Install(DiscordSocketClient client)
{
// Here, we will inject the ServiceProvider with
// all of the services our client will use.
_serviceCollection.AddSingleton(client)
_serviceCollection.AddSingleton(new NotificationService())
_serviceCollection.AddSingleton(new DatabaseService())
// ...
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
}
}
// Here, we will inject the ServiceProvider with
// all of the services our client will use.
_services = new ServiceCollection()
.AddSingleton(client)
.AddSingleton(_commands)
// You can pass in an instance of the desired type
.AddSingleton(new NotificationService())
// ...or by using the generic method.
.AddSingleton<DatabaseService>()
.BuildServiceProvider();
// ...
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
}

+ 1
- 1
docs/guides/commands/samples/empty-module.cs View File

@@ -1,6 +1,6 @@
using Discord.Commands; using Discord.Commands;


public class InfoModule : ModuleBase
public class InfoModule : ModuleBase<SocketCommandContext>
{ {
} }

+ 2
- 2
docs/guides/commands/samples/groups.cs View File

@@ -1,8 +1,8 @@
[Group("admin")] [Group("admin")]
public class AdminModule : ModuleBase
public class AdminModule : ModuleBase<SocketCommandContext>
{ {
[Group("clean")] [Group("clean")]
public class CleanModule : ModuleBase
public class CleanModule : ModuleBase<SocketCommandContext>
{ {
// ~admin clean 15 // ~admin clean 15
[Command] [Command]


+ 29
- 30
docs/guides/commands/samples/module.cs View File

@@ -1,42 +1,41 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;

// Create a module with no prefix // Create a module with no prefix
public class Info : ModuleBase
public class Info : ModuleBase<SocketCommandContext>
{ {
// ~say hello -> hello
[Command("say"), Summary("Echos a message.")]
public async Task Say([Remainder, Summary("The text to echo")] string echo)
{
// ReplyAsync is a method on ModuleBase
await ReplyAsync(echo);
}
// ~say hello -> hello
[Command("say")]
[Summary("Echos a message.")]
public async Task SayAsync([Remainder] [Summary("The text to echo")] string echo)
{
// ReplyAsync is a method on ModuleBase
await ReplyAsync(echo);
}
} }


// Create a module with the 'sample' prefix // Create a module with the 'sample' prefix
[Group("sample")] [Group("sample")]
public class Sample : ModuleBase
public class Sample : ModuleBase<SocketCommandContext>
{ {
// ~sample square 20 -> 400
[Command("square"), Summary("Squares a number.")]
public async Task Square([Summary("The number to square.")] int num)
{
// We can also access the channel from the Command Context.
await Context.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}");
}
// ~sample square 20 -> 400
[Command("square")]
[Summary("Squares a number.")]
public async Task SquareAsync([Summary("The number to square.")] int num)
{
// We can also access the channel from the Command Context.
await Context.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}");
}


// ~sample userinfo --> foxbot#0282
// ~sample userinfo --> foxbot#0282
// ~sample userinfo @Khionu --> Khionu#8708 // ~sample userinfo @Khionu --> Khionu#8708
// ~sample userinfo Khionu#8708 --> Khionu#8708 // ~sample userinfo Khionu#8708 --> Khionu#8708
// ~sample userinfo Khionu --> Khionu#8708 // ~sample userinfo Khionu --> Khionu#8708
// ~sample userinfo 96642168176807936 --> Khionu#8708 // ~sample userinfo 96642168176807936 --> Khionu#8708
// ~sample whois 96642168176807936 --> Khionu#8708
[Command("userinfo"), Summary("Returns info about the current user, or the user parameter, if one passed.")]
[Alias("user", "whois")]
public async Task UserInfo([Summary("The (optional) user to get info for")] IUser user = null)
{
var userInfo = user ?? Context.Client.CurrentUser;
await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}");
}
}
// ~sample whois 96642168176807936 --> Khionu#8708
[Command("userinfo")]
[Summary("Returns info about the current user, or the user parameter, if one passed.")]
[Alias("user", "whois")]
public async Task UserInfoAsync([Summary("The (optional) user to get info for")] SocketUser user = null)
{
var userInfo = user ?? Context.Client.CurrentUser;
await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}");
}
}

+ 7
- 4
docs/guides/concepts/entities.md View File

@@ -12,7 +12,7 @@ Discord API.
### Inheritance ### Inheritance


Due to the nature of the Discord API, some entities are designed with Due to the nature of the Discord API, some entities are designed with
multiple variants, for example, `SocketUser` and `SocketGuildUser`.
multiple variants; for example, `SocketUser` and `SocketGuildUser`.


All models will contain the most detailed version of an entity All models will contain the most detailed version of an entity
possible, even if the type is less detailed. possible, even if the type is less detailed.
@@ -61,8 +61,11 @@ a variant of the type that you need.
### Tips ### Tips


Avoid using boxing-casts to coerce entities into a variant, use the Avoid using boxing-casts to coerce entities into a variant, use the
`as` keyword, and a null-conditional operator.
[`as`] keyword, and a null-conditional operator instead.


This allows you to write safer code, and avoid InvalidCastExceptions.
This allows you to write safer code and avoid [InvalidCastExceptions].


For example, `(message.Author as SocketGuildUser)?.Nickname`.
For example, `(message.Author as SocketGuildUser)?.Nickname`.

[`as`]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as
[InvalidCastExceptions]: https://msdn.microsoft.com/en-us/library/system.invalidcastexception(v=vs.110).aspx

+ 10
- 10
docs/guides/concepts/events.md View File

@@ -4,27 +4,27 @@ title: Working with Events


Events in Discord.Net are consumed in a similar manner to the standard Events in Discord.Net are consumed in a similar manner to the standard
convention, with the exception that every event must be of the type convention, with the exception that every event must be of the type
`System.Threading.Tasks.Task`, and instead of using EventArgs, the
`System.Threading.Tasks.Task` and instead of using `EventArgs`, the
event's parameters are passed directly into the handler. event's parameters are passed directly into the handler.


This allows for events to be handled in an async context directly,
instead of relying on async void.
This allows for events to be handled in an async context directly
instead of relying on `async void`.


### Usage ### Usage


To receive data from an event, hook into it using C#'s delegate To receive data from an event, hook into it using C#'s delegate
event pattern. event pattern.


You may opt either to hook an event to an anonymous function (lambda)
You may either opt to hook an event to an anonymous function (lambda)
or a named function. or a named function.


### Safety ### Safety


All events are designed to be thread-safe, in that events are executed
synchronously off the gateway task, in the same context as the gateway
All events are designed to be thread-safe; events are executed
synchronously off the gateway task in the same context as the gateway
task. task.


As a side effect, this makes it possible to deadlock the gateway task,
As a side effect, this makes it possible to deadlock the gateway task
and kill a connection. As a general rule of thumb, any task that takes and kill a connection. As a general rule of thumb, any task that takes
longer than three seconds should **not** be awaited directly in the longer than three seconds should **not** be awaited directly in the
context of an event, but should be wrapped in a `Task.Run` or context of an event, but should be wrapped in a `Task.Run` or
@@ -62,7 +62,7 @@ This pattern is typically only found on `EntityUpdated` events.


An event handler with a signature of `Func<Cacheable, Entity, Task>` An event handler with a signature of `Func<Cacheable, Entity, Task>`
means that the `before` state of the entity was not provided by the means that the `before` state of the entity was not provided by the
API, so it can either be pulled from the client's cache, or
API, so it can either be pulled from the client's cache or
downloaded from the API. downloaded from the API.


See the documentation for [Cacheable] for more information on this See the documentation for [Cacheable] for more information on this
@@ -76,8 +76,8 @@ object.


### Tips ### Tips


Many events relating to a Message entity, e.g. `MessageUpdated`
and `ReactionAdded` rely on the client's message cache, which is
Many events relating to a Message entity (i.e. `MessageUpdated` and
`ReactionAdded`) rely on the client's message cache, which is
**not** enabled by default. Set the `MessageCacheSize` flag in **not** enabled by default. Set the `MessageCacheSize` flag in
[DiscordSocketConfig] to enable it. [DiscordSocketConfig] to enable it.



+ 7
- 2
docs/guides/concepts/logging.md View File

@@ -14,12 +14,14 @@ section.
### Usage ### Usage


To receive log events, simply hook the discord client's log method To receive log events, simply hook the discord client's log method
to a Task with a single parameter of type [LogMessage]
to a `Task` with a single parameter of type [LogMessage].


It is recommended that you use an established function instead of a It is recommended that you use an established function instead of a
lambda for handling logs, because most [addons] accept a reference
lambda for handling logs, because most addons accept a reference
to a logging function to write their own messages. to a logging function to write their own messages.


[LogMessage]: xref:Discord.LogMessage

### Usage in Commands ### Usage in Commands


Discord.Net's [CommandService] also provides a log event, identical Discord.Net's [CommandService] also provides a log event, identical
@@ -29,6 +31,9 @@ Data logged through this event is typically coupled with a
[CommandException], where information about the command's context [CommandException], where information about the command's context
and error can be found and handled. and error can be found and handled.


[CommandService]: xref:Discord.Commands.CommandService
[CommandException]: xref:Discord.Commands.CommandException

#### Samples #### Samples


[!code-csharp[Logging Sample](samples/logging.cs)] [!code-csharp[Logging Sample](samples/logging.cs)]


+ 65
- 60
docs/guides/getting_started/installing.md View File

@@ -2,84 +2,87 @@
title: Installing Discord.Net title: Installing Discord.Net
--- ---


Discord.Net is distributed through the NuGet package manager, and it is
recommended to use NuGet to get started.
Discord.Net is distributed through the NuGet package manager, and it
is recommended to use NuGet to get started.


Optionally, you may compile from source and install yourself. Optionally, you may compile from source and install yourself.


# Supported Platforms # Supported Platforms


Currently, Discord.Net targets [.NET Standard] 1.3, and offers support for
.NET Standard 1.1. If your application will be targeting .NET Standard 1.1,
please see the [additional steps](#installing-on-net-standard-11).
Currently, Discord.Net targets [.NET Standard] 1.3 and offers support
for .NET Standard 1.1. If your application will be targeting .NET
Standard 1.1, please see the [additional steps].


Since Discord.Net is built on the .NET Standard, it is also recommended to
create applications using [.NET Core], though you are not required to. When
using .NET Framework, it is suggested to target `.NET 4.6.1` or higher.
Since Discord.Net is built on the .NET Standard, it is also
recommended to create applications using [.NET Core], though not
required. When using .NET Framework, it is suggested to target
`.NET Framework 4.6.1` or higher.


[.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library [.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library
[.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/ [.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/
[additional steps]: #installing-on-net-standard-11


# Installing with NuGet # Installing with NuGet


Release builds of Discord.Net 1.0 will be published to the Release builds of Discord.Net 1.0 will be published to the
[official NuGet feed]. [official NuGet feed].


Development builds of Discord.Net 1.0, as well as [addons](TODO) are published
to our development [MyGet feed].
Development builds of Discord.Net 1.0, as well as addons *(TODO)* are
published to our development [MyGet feed].


Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json`


Not sure how to add a direct feed? See how [with Visual Studio]
or [without Visual Studio](#configuring-nuget-without-visual-studio)
Not sure how to add a direct feed? See how [with Visual Studio] or
[without Visual Studio].


[official NuGet feed]: https://nuget.org [official NuGet feed]: https://nuget.org
[MyGet feed]: https://www.myget.org/feed/Packages/discord-net [MyGet feed]: https://www.myget.org/feed/Packages/discord-net
[with Visual Studio]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources [with Visual Studio]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources
[without Visual Studio]: #configuring-nuget-without-visual-studio


## Using Visual Studio ## Using Visual Studio


1. Create a solution for your bot
2. In Solution Explorer, find the 'Dependencies' element under your bot's
project
3. Right click on 'Dependencies', and select 'Manage NuGet packages'
![Step 3](images/install-vs-deps.png)
4. In the 'browse' tab, search for 'Discord.Net'

> [!TIP] > [!TIP]
Don't forget to change your package source if you're installing from the
developer feed.
Also make sure to check 'Enable Prereleases' if installing a dev build!

5. Install the 'Discord.Net' package

>Don't forget to change your package source if you're installing from
the developer feed.
>Also make sure to check "Enable Prereleases" if installing a dev
build!

1. Create a solution for your bot.
2. In Solution Explorer, find the "Dependencies" element under your
bot's project.
3. Right click on "Dependencies", and select "Manage NuGet packages."
![Step 3](images/install-vs-deps.png)
4. In the "Browse" tab, search for `Discord.Net`.
5. Install the `Discord.Net` package.
![Step 5](images/install-vs-nuget.png) ![Step 5](images/install-vs-nuget.png)


## Using JetBrains Rider ## Using JetBrains Rider


1. Create a new solution for your bot
2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for Solution)
![Step 2](images/install-rider-nuget-manager.png)
3. In the 'Packages' tab, search for 'Discord.Net'
![Step 3](images/install-rider-search.png)

> [!TIP] > [!TIP]
Make sure to check the 'Prerelease' box if installing a dev build!
Make sure to check the "Prerelease" box if installing a dev build!


4. Install by adding the package to your project
1. Create a new solution for your bot.
2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for
Solution).
![Step 2](images/install-rider-nuget-manager.png)
3. In the "Packages" tab, search for `Discord.Net`.
![Step 3](images/install-rider-search.png)
4. Install by adding the package to your project.
![Step 4](images/install-rider-add.png) ![Step 4](images/install-rider-add.png)


## Using Visual Studio Code ## Using Visual Studio Code


1. Create a new project for your bot
2. Add Discord.Net to your .csproj
> [!TIP]
Don't forget to add the package source to a [NuGet.Config file] if
you're installing from the developer feed.

1. Create a new project for your bot.
2. Add `Discord.Net` to your .csproj.


[!code-xml[Sample .csproj](samples/project.csproj)] [!code-xml[Sample .csproj](samples/project.csproj)]


> [!TIP]
Don't forget to add the package source to a [NuGet.Config file](#configuring-nuget-without-visual-studio) if you're installing from the
developer feed.
[NuGet.Config file]: #configuring-nuget-without-visual-studio


# Compiling from Source # Compiling from Source


@@ -90,8 +93,8 @@ In order to compile Discord.Net, you require the following:
- [Visual Studio 2017](https://www.visualstudio.com/) - [Visual Studio 2017](https://www.visualstudio.com/)
- [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) - [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk)


The .NET Core and Docker (Preview) workload is required during Visual Studio
installation.
The .NET Core and Docker (Preview) workload is required during Visual
Studio installation.


### Using Command Line ### Using Command Line


@@ -101,26 +104,27 @@ installation.


## Installing on .NET Standard 1.1 ## Installing on .NET Standard 1.1


For applications targeting a runtime corresponding with .NET Standard 1.1 or 1.2,
the builtin WebSocket and UDP provider will not work. For applications which
utilize a WebSocket connection to Discord (WebSocket or RPC), third-party
provider packages will need to be installed and configured.
For applications targeting a runtime corresponding with .NET Standard
1.1 or 1.2, the builtin WebSocket and UDP provider will not work. For
applications which utilize a WebSocket connection to Discord
(WebSocket or RPC), third-party provider packages will need to be
installed and configured.


First, install the following packages through NuGet, or compile yourself, if
you prefer:
First, install the following packages through NuGet, or compile
yourself, if you prefer:


- Discord.Net.Providers.WS4Net - Discord.Net.Providers.WS4Net
- Discord.Net.Providers.UDPClient - Discord.Net.Providers.UDPClient


Note that `Discord.Net.Providers.UDPClient` is _only_ required if your bot will
be utilizing voice chat.
Note that `Discord.Net.Providers.UDPClient` is _only_ required if your
bot will be utilizing voice chat.


Next, you will need to configure your [DiscordSocketClient] to use these custom
providers over the default ones.
Next, you will need to configure your [DiscordSocketClient] to use
these custom providers over the default ones.


To do this, set the `WebSocketProvider` and optionally `UdpSocketProvider`
properties on the [DiscordSocketConfig] that you are passing into your
client.
To do this, set the `WebSocketProvider` and the optional
`UdpSocketProvider` properties on the [DiscordSocketConfig] that you
are passing into your client.


[!code-csharp[NET Standard 1.1 Example](samples/netstd11.cs)] [!code-csharp[NET Standard 1.1 Example](samples/netstd11.cs)]


@@ -129,13 +133,14 @@ client.


## Configuring NuGet without Visual Studio ## Configuring NuGet without Visual Studio


If you plan on deploying your bot or developing outside of Visual Studio, you
will need to create a local NuGet configuration file for your project.
If you plan on deploying your bot or developing outside of Visual
Studio, you will need to create a local NuGet configuration file for
your project.


To do this, create a file named `nuget.config` alongside the root of your
application, where the project solution is located.
To do this, create a file named `nuget.config` alongside the root of
your application, where the project solution is located.


Paste the following snippets into this configuration file, adding any additional
feeds as necessary.
Paste the following snippets into this configuration file, adding any
additional feeds as necessary.


[!code-xml[NuGet Configuration](samples/nuget.config)] [!code-xml[NuGet Configuration](samples/nuget.config)]

+ 72
- 62
docs/guides/getting_started/intro.md View File

@@ -13,42 +13,46 @@ diverse commands later, but for now, it is a good starting point.
Before you can begin writing your bot, it is necessary to create a bot Before you can begin writing your bot, it is necessary to create a bot
account on Discord. account on Discord.


1. Visit the [Discord Applications Portal]
2. Create a New Application
1. Visit the [Discord Applications Portal].
2. Create a New Application.
3. Give the application a name (this will be the bot's initial 3. Give the application a name (this will be the bot's initial
username). username).
4. Create the Application
![Step 4](images/intro-create-app.png)
5. In the application review page, click **Create a Bot User**
![Step 5](images/intro-create-bot.png)
6. Confirm the popup
7. If this bot will be public, check 'Public Bot'.
**Do not tick any other options!**
4. Create the Application.
![Step 4](images/intro-create-app.png)
5. In the application review page, click **Create a Bot User**.
![Step 5](images/intro-create-bot.png)
6. Confirm the popup.
7. If this bot will be public, check "Public Bot." **Do not tick any
other options!**


[Discord Applications Portal]: https://discordapp.com/developers/applications/me [Discord Applications Portal]: https://discordapp.com/developers/applications/me


## Adding your bot to a server ## Adding your bot to a server


Bots **can not** use invite links, they must be explicitly invited
Bots **cannot** use invite links, they must be explicitly invited
through the OAuth2 flow. through the OAuth2 flow.


1. Open your bot's application on the [Discord Applications Portal]
1. Open your bot's application on the [Discord Applications Portal].
2. Retrieve the app's **Client ID**. 2. Retrieve the app's **Client ID**.
![Step 2](images/intro-client-id.png)
![Step 2](images/intro-client-id.png)
3. Create an OAuth2 authorization URL 3. Create an OAuth2 authorization URL
`https://discordapp.com/oauth2/authorize?client_id=<CLIENT ID>&scope=bot` `https://discordapp.com/oauth2/authorize?client_id=<CLIENT ID>&scope=bot`
4. Open the authorization URL in your browser
5. Select a server

>[!NOTE]
Only servers where you have the `MANAGE_SERVER` permission will be
present in this list.
4. Open the authorization URL in your browser.
5. Select a server.
6. Click on authorize.
>[!NOTE]
Only servers where you have the `MANAGE_SERVER` permission will be
present in this list.
![Step 6](images/intro-add-bot.png)


6. Click authorize

![Step 6](images/intro-add-bot.png)


## Connecting to Discord ## Connecting to Discord


@@ -57,10 +61,10 @@ do that now. (see the [Installing](installing.md) section)


### Async ### Async


Discord.Net uses .NET's Task-based Asynchronous Pattern ([TAP])
Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)]
extensively - nearly every operation is asynchronous. extensively - nearly every operation is asynchronous.


It is highly recommended that these operations be awaited in a
It is highly recommended that these operations are awaited in a
properly established async context whenever possible. Establishing an properly established async context whenever possible. Establishing an
async context can be problematic, but not hard. async context can be problematic, but not hard.


@@ -70,27 +74,29 @@ async main.


[!code-csharp[Async Context](samples/intro/async-context.cs)] [!code-csharp[Async Context](samples/intro/async-context.cs)]


As a result of this, your program will now start, and immidiately
jump into an async context. This will allow us later on to create a
connection to Discord, without needing to worry about setting up the
As a result of this, your program will now start and immediately
jump into an async context. This will allow us to create a connection
to Discord later on without needing to worry about setting up the
correct async implementation. correct async implementation.


>[!TIP] >[!TIP]
If your application throws any exceptions within an async context, If your application throws any exceptions within an async context,
they will be thrown all the way back up to the first non-async method.
Since our first non-async method is the program's Main method, this
they will be thrown all the way back up to the first non-async method;
since our first non-async method is the program's `Main` method, this
means that **all** unhandled exceptions will be thrown up there, which means that **all** unhandled exceptions will be thrown up there, which
will crash your application. Discord.Net will prevent exceptions in will crash your application. Discord.Net will prevent exceptions in
event handlers from crashing your program, but any exceptions in your event handlers from crashing your program, but any exceptions in your
async main **will** cause the application to crash. async main **will** cause the application to crash.


[Task-based Asynchronous Pattern (TAP)]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async

### Creating a logging method ### Creating a logging method


Before we create and configure a Discord client, we will add a method Before we create and configure a Discord client, we will add a method
to handle Discord.Net's log events. to handle Discord.Net's log events.


To allow agnostic support of as many log providers as possible, we To allow agnostic support of as many log providers as possible, we
log information through a Log event, with a proprietary LogMessage
log information through a `Log` event with a proprietary `LogMessage`
parameter. See the [API Documentation] for this event. parameter. See the [API Documentation] for this event.


If you are using your own logging framework, this is where you would If you are using your own logging framework, this is where you would
@@ -99,10 +105,12 @@ the Console.


[!code-csharp[Async Context](samples/intro/logging.cs)] [!code-csharp[Async Context](samples/intro/logging.cs)]


[API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log

### Creating a Discord Client ### Creating a Discord Client


Finally, we can create a connection to Discord. Since we are writing Finally, we can create a connection to Discord. Since we are writing
a bot, we will be using a [DiscordSocketClient], along with socket
a bot, we will be using a [DiscordSocketClient] along with socket
entities. See the [terminology](terminology.md) if you're unsure of entities. See the [terminology](terminology.md) if you're unsure of
the differences. the differences.


@@ -110,22 +118,24 @@ To do so, create an instance of [DiscordSocketClient] in your async
main, passing in a configuration object only if necessary. For most main, passing in a configuration object only if necessary. For most
users, the default will work fine. users, the default will work fine.


Before connecting, we should hook the client's log event to the
Before connecting, we should hook the client's `Log` event to the
log handler that was just created. Events in Discord.Net work log handler that was just created. Events in Discord.Net work
similarly to other events in C#, so hook this event the way that similarly to other events in C#, so hook this event the way that
you typically would. you typically would.


Next, you will need to 'login to Discord' with the `LoginAsync` method.
Next, you will need to "login to Discord" with the `LoginAsync`
method.


You may create a variable to hold your bot's token (this can be found You may create a variable to hold your bot's token (this can be found
on your bot's application page on the [Discord Applications Portal]). on your bot's application page on the [Discord Applications Portal]).

![Token](images/intro-token.png) ![Token](images/intro-token.png)


>[!IMPORTANT] >[!IMPORTANT]
Your bot's token can be used to gain total access to your bot, so Your bot's token can be used to gain total access to your bot, so
**do __NOT__ share this token with anyone!** It may behoove you to
store this token in an external file if you plan on distributing the
source code for your bot.
**do __NOT__ share this token with anyone else!** It may behoove you
to store this token in an external file if you plan on distributing
the source code for your bot.


We may now invoke the client's `StartAsync` method, which will We may now invoke the client's `StartAsync` method, which will
start connection/reconnection logic. It is important to note that start connection/reconnection logic. It is important to note that
@@ -134,14 +144,9 @@ start connection/reconnection logic. It is important to note that
Any methods that rely on the client's state should go in an event Any methods that rely on the client's state should go in an event
handler. handler.


>[!NOTE]
Connection logic is incomplete as of the current build. Events will
soon be added to indicate when the client's state is ready for use;
(rewrite this section when possible)

Finally, we will want to block the async main method from returning Finally, we will want to block the async main method from returning
until after the application is exited. To do this, we can await an until after the application is exited. To do this, we can await an
infinite delay, or any other blocking method, such as reading from
infinite delay or any other blocking method, such as reading from
the console. the console.


The following lines can now be added: The following lines can now be added:
@@ -154,51 +159,55 @@ online in Discord.
>[!TIP] >[!TIP]
Encountering a `PlatformNotSupportedException` when starting your bot? Encountering a `PlatformNotSupportedException` when starting your bot?
This means that you are targeting a platform where .NET's default This means that you are targeting a platform where .NET's default
WebSocket client is not supported. Refer to the [installing guide]
WebSocket client is not supported. Refer to the [installation guide]
for how to fix this. for how to fix this.


[TAP]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async
[API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient
[installing guide]: installing.md#installing-on-net-standard-11
[installation guide]: installing.md#installing-on-net-standard-11


### Handling a 'ping' ### Handling a 'ping'


>[!WARNING]
Please note that this is *not* a proper way to create a command.
Use the `CommandService` provided by the library instead, as explained
in the [Command Guide] section.

Now that we have learned how to open a connection to Discord, we can Now that we have learned how to open a connection to Discord, we can
begin handling messages that users are sending. begin handling messages that users are sending.


To start out, our bot will listen for any message where the content To start out, our bot will listen for any message where the content
is equal to `!ping`, and respond back with `Pong!`.
is equal to `!ping` and respond back with "Pong!".


Since we want to listen for new messages, the event to hook in to
Since we want to listen for new messages, the event to hook into
is [MessageReceived]. is [MessageReceived].


In your program, add a method that matches the signature of the In your program, add a method that matches the signature of the
MessageReceived event - it must be a method (`Func`) that returns the
type `Task`, and takes a single parameter, a [SocketMessage]. Also,
`MessageReceived` event - it must be a method (`Func`) that returns
the type `Task` and takes a single parameter, a [SocketMessage]. Also,
since we will be sending data to Discord in this method, we will flag since we will be sending data to Discord in this method, we will flag
it as `async`. it as `async`.


In this method, we will add an `if` block, to determine if the message
In this method, we will add an `if` block to determine if the message
content fits the rules of our scenario - recall that it must be equal content fits the rules of our scenario - recall that it must be equal
to `!ping`. to `!ping`.


Inside the branch of this condition, we will want to send a message Inside the branch of this condition, we will want to send a message
back to the channel from which the message came - `Pong!`. To find the
channel, look for the `Channel` property on the message parameter.
back to the channel from which the message comes from - "Pong!". To
find the channel, look for the `Channel` property on the message
parameter.


Next, we will want to send a message to this channel. Since the Next, we will want to send a message to this channel. Since the
channel object is of type [SocketMessageChannel], we can invoke the channel object is of type [SocketMessageChannel], we can invoke the
`SendMessageAsync` instance method. For the message content, send back `SendMessageAsync` instance method. For the message content, send back
a string containing 'Pong!'.
a string containing "Pong!".


You should have now added the following lines: You should have now added the following lines:


[!code-csharp[Message](samples/intro/message.cs)] [!code-csharp[Message](samples/intro/message.cs)]


Now, your first bot is complete. You may continue to add on to this
if you desire, but for any bot that will be carrying out multiple
commands, it is strongly encouraged to use the command framework, as
Now your first bot is complete. You may continue to add on to this
if you desire, but for any bots that will be carrying out multiple
commands, it is strongly recommended to use the command framework as
shown below. shown below.


For your reference, you may view the [completed program]. For your reference, you may view the [completed program].
@@ -207,13 +216,14 @@ For your reference, you may view the [completed program].
[SocketMessage]: xref:Discord.WebSocket.SocketMessage [SocketMessage]: xref:Discord.WebSocket.SocketMessage
[SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel [SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel
[completed program]: samples/intro/complete.cs [completed program]: samples/intro/complete.cs
[Command Guide]: ../commands/commands.md


# Building a bot with commands # Building a bot with commands


This section will show you how to write a program that is ready for This section will show you how to write a program that is ready for
[commands](commands/commands.md). Note that this will not be explaining _how_
to write commands or services, it will only be covering the general
structure.
[Commands](../commands/commands.md). Note that we will not be
explaining _how_ to write Commands or Services, it will only be
covering the general structure.


For reference, view an [annotated example] of this structure. For reference, view an [annotated example] of this structure.


@@ -224,4 +234,4 @@ should be to separate the program (initialization and command handler),
the modules (handle commands), and the services (persistent storage, the modules (handle commands), and the services (persistent storage,
pure functions, data manipulation). pure functions, data manipulation).


**todo:** diagram of bot structure
**todo:** diagram of bot structure

+ 2
- 2
docs/guides/getting_started/samples/intro/message.cs View File

@@ -1,7 +1,7 @@
public async Task MainAsync() public async Task MainAsync()
{ {
// client.Log ... // client.Log ...
client.MessageReceived += MessageReceived;
_client.MessageReceived += MessageReceived;
// ... // ...
} }


@@ -11,4 +11,4 @@ private async Task MessageReceived(SocketMessage message)
{ {
await message.Channel.SendMessageAsync("Pong!"); await message.Channel.SendMessageAsync("Pong!");
} }
}
}

+ 20
- 13
docs/guides/getting_started/samples/intro/structure.cs View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Discord; using Discord;
@@ -8,12 +9,6 @@ using Discord.WebSocket;


class Program class Program
{ {
private readonly DiscordSocketClient _client;
// Keep the CommandService and IServiceCollection around for use with commands.
private readonly IServiceCollection _map = new ServiceCollection();
private readonly CommandService _commands = new CommandService();

// Program entry point // Program entry point
static void Main(string[] args) static void Main(string[] args)
{ {
@@ -22,6 +17,13 @@ class Program
new Program().MainAsync().GetAwaiter().GetResult(); new Program().MainAsync().GetAwaiter().GetResult();
} }


private readonly DiscordSocketClient _client;
// Keep the CommandService and IServiceCollection around for use with commands.
// These two types require you install the Discord.Net.Commands package.
private readonly IServiceCollection _map = new ServiceCollection();
private readonly CommandService _commands = new CommandService();

private Program() private Program()
{ {
_client = new DiscordSocketClient(new DiscordSocketConfig _client = new DiscordSocketClient(new DiscordSocketConfig
@@ -48,7 +50,6 @@ class Program
// that ask for a Func<LogMessage, Task>. // that ask for a Func<LogMessage, Task>.
private static Task Logger(LogMessage message) private static Task Logger(LogMessage message)
{ {
var cc = Console.ForegroundColor;
switch (message.Severity) switch (message.Severity)
{ {
case LogSeverity.Critical: case LogSeverity.Critical:
@@ -66,8 +67,8 @@ class Program
Console.ForegroundColor = ConsoleColor.DarkGray; Console.ForegroundColor = ConsoleColor.DarkGray;
break; break;
} }
Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message}");
Console.ForegroundColor = cc;
Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message} {message.Exception}");
Console.ResetColor();
// If you get an error saying 'CompletedTask' doesn't exist, // If you get an error saying 'CompletedTask' doesn't exist,
// your project is targeting .NET 4.5.2 or lower. You'll need // your project is targeting .NET 4.5.2 or lower. You'll need
@@ -80,7 +81,7 @@ class Program


private async Task MainAsync() private async Task MainAsync()
{ {
// Centralize the logic for commands into a seperate method.
// Centralize the logic for commands into a separate method.
await InitCommands(); await InitCommands();


// Login and connect. // Login and connect.
@@ -88,7 +89,7 @@ class Program
await _client.StartAsync(); await _client.StartAsync();


// Wait infinitely so your bot actually stays connected. // Wait infinitely so your bot actually stays connected.
await Task.Delay(-1);
await Task.Delay(Timeout.Infinite);
} }


private IServiceProvider _services; private IServiceProvider _services;
@@ -105,10 +106,11 @@ class Program
_services = _map.BuildServiceProvider(); _services = _map.BuildServiceProvider();


// Either search the program and add all Module classes that can be found. // Either search the program and add all Module classes that can be found.
// Module classes *must* be marked 'public' or they will be ignored.
// Module classes MUST be marked 'public' or they will be ignored.
await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
// Or add Modules manually if you prefer to be a little more explicit: // Or add Modules manually if you prefer to be a little more explicit:
await _commands.AddModuleAsync<SomeModule>(); await _commands.AddModuleAsync<SomeModule>();
// Note that the first one is 'Modules' (plural) and the second is 'Module' (singular).


// Subscribe a handler to see if a message invokes a command. // Subscribe a handler to see if a message invokes a command.
_client.MessageReceived += HandleCommandAsync; _client.MessageReceived += HandleCommandAsync;
@@ -120,6 +122,11 @@ class Program
var msg = arg as SocketUserMessage; var msg = arg as SocketUserMessage;
if (msg == null) return; if (msg == null) return;


// We don't want the bot to respond to itself or other bots.
// NOTE: Selfbots should invert this first check and remove the second
// as they should ONLY be allowed to respond to messages from the same account.
if (msg.Author.Id == _client.CurrentUser.Id || msg.Author.IsBot) return;
// Create a number to track where the prefix ends and the command begins // Create a number to track where the prefix ends and the command begins
int pos = 0; int pos = 0;
// Replace the '!' with whatever character // Replace the '!' with whatever character
@@ -132,7 +139,7 @@ class Program
var context = new SocketCommandContext(_client, msg); var context = new SocketCommandContext(_client, msg);
// Execute the command. (result does not indicate a return value, // Execute the command. (result does not indicate a return value,
// rather an object stating if the command executed succesfully).
// rather an object stating if the command executed successfully).
var result = await _commands.ExecuteAsync(context, pos, _services); var result = await _commands.ExecuteAsync(context, pos, _services);


// Uncomment the following lines if you want the bot // Uncomment the following lines if you want the bot


+ 1
- 1
docs/guides/getting_started/samples/project.csproj View File

@@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>


<ItemGroup> <ItemGroup>
<PackageReference Include="Discord.Net" Version="1.0.0-rc-00617" />
<PackageReference Include="Discord.Net" Version="1.*" />
</ItemGroup> </ItemGroup>


</Project> </Project>

+ 17
- 15
docs/guides/getting_started/terminology.md View File

@@ -7,32 +7,34 @@ title: Terminology


## Preface ## Preface


Most terms for objects remain the same between 0.9 and 1.0. The major difference is that the ``Server`` is now called ``Guild``, to stay in line with Discord internally
Most terms for objects remain the same between 0.9 and 1.0. The major
difference is that the ``Server`` is now called ``Guild`` to stay in
line with Discord internally.


## Implementation Specific Entities ## Implementation Specific Entities


Discord.Net 1.0 is split into a core library, and three different
implementations - Discord.Net.Core, Discord.Net.Rest, Discord.Net.Rpc,
and Discord.Net.WebSockets.
Discord.Net 1.0 is split into a core library and three different
implementations - `Discord.Net.Core`, `Discord.Net.Rest`,
`Discord.Net.Rpc`, and `Discord.Net.WebSockets`.


As a bot developer, you will only need to use Discord.Net.WebSockets,
As a bot developer, you will only need to use `Discord.Net.WebSockets`,
but you should be aware of the differences between them. but you should be aware of the differences between them.


`Discord.Net.Core` provides a set of interfaces that model Discord's
`Discord.Net.Core` provides a set of interfaces that models Discord's
API. These interfaces are consistent throughout all implementations of API. These interfaces are consistent throughout all implementations of
Discord.Net, and if you are writing an implementation-agnostic library Discord.Net, and if you are writing an implementation-agnostic library
or addon, you can rely on the core interfaces to ensure that your or addon, you can rely on the core interfaces to ensure that your
addon will run on all platforms. addon will run on all platforms.


`Discord.Net.Rest` provides a set of concrete classes to be used `Discord.Net.Rest` provides a set of concrete classes to be used
**strictly** with the REST portion of Discord's API. Entities in
this implementation are prefixed with `Rest`, e.g. `RestChannel`.
**strictly** with the REST portion of Discord's API. Entities in this
implementation are prefixed with `Rest` (e.g. `RestChannel`).


`Discord.Net.Rpc` provides a set of concrete classes that are used with
Discord's RPC API. Entities in this implementation are prefixed with
`Rpc`, e.g. `RpcChannel`.
`Discord.Net.Rpc` provides a set of concrete classes that are used
with Discord's RPC API. Entities in this implementation are prefixed
with `Rpc` (e.g. `RpcChannel`).


`Discord.Net.WebSocket` provides a set of concrete classes that are used
primarily with Discord's WebSocket API, or entities that are kept in
cache. When developing bots, you will be using this implementation. All
entities are prefixed with `Socket`, e.g. `SocketChannel`.
`Discord.Net.WebSocket` provides a set of concrete classes that are
used primarily with Discord's WebSocket API or entities that are kept
in cache. When developing bots, you will be using this implementation.
All entities are prefixed with `Socket` (e.g. `SocketChannel`).

+ 4
- 5
docs/guides/voice/samples/audio_create_ffmpeg.cs View File

@@ -1,11 +1,10 @@
private Process CreateStream(string path) private Process CreateStream(string path)
{ {
var ffmpeg = new ProcessStartInfo
return Process.Start(new ProcessStartInfo
{ {
FileName = "ffmpeg", FileName = "ffmpeg",
Arguments = $"-i {path} -ac 2 -f s16le -ar 48000 pipe:1",
Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -ac 2 -f s16le -ar 48000 pipe:1",
UseShellExecute = false, UseShellExecute = false,
RedirectStandardOutput = true, RedirectStandardOutput = true,
};
return Process.Start(ffmpeg);
}
});
}

+ 7
- 5
docs/guides/voice/samples/audio_ffmpeg.cs View File

@@ -1,9 +1,11 @@
private async Task SendAsync(IAudioClient client, string path) private async Task SendAsync(IAudioClient client, string path)
{ {
// Create FFmpeg using the previous example // Create FFmpeg using the previous example
var ffmpeg = CreateStream(path);
var output = ffmpeg.StandardOutput.BaseStream;
var discord = client.CreatePCMStream(AudioApplication.Mixed);
await output.CopyToAsync(discord);
await discord.FlushAsync();
using (var ffmpeg = CreateStream(path))
using (var output = ffmpeg.StandardOutput.BaseStream)
using (var discord = client.CreatePCMStream(AudioApplication.Mixed))
{
try { await output.CopyToAsync(discord); }
finally { await discord.FlushAsync(); }
}
} }

+ 1
- 1
docs/guides/voice/samples/joining_audio.cs View File

@@ -7,4 +7,4 @@ public async Task JoinChannel(IVoiceChannel channel = null)


// For the next step with transmitting audio, you would want to pass this Audio Client in to a service. // For the next step with transmitting audio, you would want to pass this Audio Client in to a service.
var audioClient = await channel.ConnectAsync(); var audioClient = await channel.ConnectAsync();
}
}

+ 4
- 4
docs/guides/voice/sending-voice.md View File

@@ -17,7 +17,7 @@ when developing on .NET Core, this is where you execute `dotnet run`
from; typically the same directory as your csproj). from; typically the same directory as your csproj).


For Windows Users, precompiled binaries are available for your For Windows Users, precompiled binaries are available for your
convienence [here](https://discord.foxbot.me/binaries/)
convienence [here](https://discord.foxbot.me/binaries/).


For Linux Users, you will need to compile [Sodium] and [Opus] from For Linux Users, you will need to compile [Sodium] and [Opus] from
source, or install them from your package manager. source, or install them from your package manager.
@@ -31,7 +31,7 @@ Joining a channel is the first step to sending audio, and will return
an [IAudioClient] to send data with. an [IAudioClient] to send data with.


To join a channel, simply await [ConnectAsync] on any instance of an To join a channel, simply await [ConnectAsync] on any instance of an
@Discord.IVoiceChannel.
@Discord.IAudioChannel.


[!code-csharp[Joining a Channel](samples/joining_audio.cs)] [!code-csharp[Joining a Channel](samples/joining_audio.cs)]


@@ -44,7 +44,7 @@ guild. To switch channels within a guild, invoke [ConnectAsync] on
another voice channel in the guild. another voice channel in the guild.


[IAudioClient]: xref:Discord.Audio.IAudioClient [IAudioClient]: xref:Discord.Audio.IAudioClient
[ConnectAsync]: xref:Discord.IVoiceChannel#Discord_IVoiceChannel_ConnectAsync
[ConnectAsync]: xref:Discord.IAudioChannel#Discord_IAudioChannel_ConnectAsync_Action_IAudioClient__


## Transmitting Audio ## Transmitting Audio


@@ -84,7 +84,7 @@ Channels should be left at `2`, unless you specified a different value
for `-ac 2` when creating FFmpeg. for `-ac 2` when creating FFmpeg.


[AudioOutStream]: xref:Discord.Audio.AudioOutStream [AudioOutStream]: xref:Discord.Audio.AudioOutStream
[IAudioClient.CreatePCMStream]: xref:Discord.Audio.IAudioClient#Discord_Audio_IAudioClient_CreatePCMStream_System_Int32_System_Int32_System_Nullable_System_Int32__System_Int32_
[IAudioClient.CreatePCMStream]: xref:Discord.Audio.IAudioClient#Discord_Audio_IAudioClient_CreateDirectPCMStream_Discord_Audio_AudioApplication_System_Nullable_System_Int32__System_Int32_


Finally, audio will need to be piped from FFmpeg's stdout into your Finally, audio will need to be piped from FFmpeg's stdout into your
AudioOutStream. This step can be as complex as you'd like it to be, but AudioOutStream. This step can be as complex as you'd like it to be, but


+ 1
- 1
src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs View File

@@ -7,6 +7,6 @@ namespace Discord.Commands
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)]
public abstract class ParameterPreconditionAttribute : Attribute public abstract class ParameterPreconditionAttribute : Attribute
{ {
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services);
public abstract Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services);
} }
} }

+ 2
- 2
src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs View File

@@ -8,11 +8,11 @@ namespace Discord.Commands
{ {
/// <summary> /// <summary>
/// Specify a group that this precondition belongs to. Preconditions of the same group require only one /// Specify a group that this precondition belongs to. Preconditions of the same group require only one
/// of the preconditions to pass in order to be successful (A || B). Specifying <see cref="Group"/> = <see cref="null"/>
/// of the preconditions to pass in order to be successful (A || B). Specifying <see cref="Group"/> = <see langword="null"/>
/// or not at all will require *all* preconditions to pass, just like normal (A &amp;&amp; B). /// or not at all will require *all* preconditions to pass, just like normal (A &amp;&amp; B).
/// </summary> /// </summary>
public string Group { get; set; } = null; public string Group { get; set; } = null;


public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services);
public abstract Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services);
} }
} }

+ 3
- 5
src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs View File

@@ -41,7 +41,7 @@ namespace Discord.Commands
GuildPermission = null; GuildPermission = null;
} }


public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{ {
IGuildUser guildUser = null; IGuildUser guildUser = null;
if (context.Guild != null) if (context.Guild != null)
@@ -57,13 +57,11 @@ namespace Discord.Commands


if (ChannelPermission.HasValue) if (ChannelPermission.HasValue)
{ {
var guildChannel = context.Channel as IGuildChannel;

ChannelPermissions perms; ChannelPermissions perms;
if (guildChannel != null)
if (context.Channel is IGuildChannel guildChannel)
perms = guildUser.GetPermissions(guildChannel); perms = guildUser.GetPermissions(guildChannel);
else else
perms = ChannelPermissions.All(guildChannel);
perms = ChannelPermissions.All(context.Channel);


if (!perms.Has(ChannelPermission.Value)) if (!perms.Has(ChannelPermission.Value))
return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}"); return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}");


+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs View File

@@ -38,7 +38,7 @@ namespace Discord.Commands
Contexts = contexts; Contexts = contexts;
} }


public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{ {
bool isValid = false; bool isValid = false;




+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Commands
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequireNsfwAttribute : PreconditionAttribute public class RequireNsfwAttribute : PreconditionAttribute
{ {
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{ {
if (context.Channel is ITextChannel text && text.IsNsfw) if (context.Channel is ITextChannel text && text.IsNsfw)
return Task.FromResult(PreconditionResult.FromSuccess()); return Task.FromResult(PreconditionResult.FromSuccess());


+ 3
- 2
src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs View File

@@ -1,4 +1,5 @@
using System;
#pragma warning disable CS0618
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;


@@ -11,7 +12,7 @@ namespace Discord.Commands
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequireOwnerAttribute : PreconditionAttribute public class RequireOwnerAttribute : PreconditionAttribute
{ {
public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{ {
switch (context.Client.TokenType) switch (context.Client.TokenType)
{ {


+ 3
- 5
src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs View File

@@ -42,7 +42,7 @@ namespace Discord.Commands
GuildPermission = null; GuildPermission = null;
} }
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{ {
var guildUser = context.User as IGuildUser; var guildUser = context.User as IGuildUser;


@@ -56,13 +56,11 @@ namespace Discord.Commands


if (ChannelPermission.HasValue) if (ChannelPermission.HasValue)
{ {
var guildChannel = context.Channel as IGuildChannel;

ChannelPermissions perms; ChannelPermissions perms;
if (guildChannel != null)
if (context.Channel is IGuildChannel guildChannel)
perms = guildUser.GetPermissions(guildChannel); perms = guildUser.GetPermissions(guildChannel);
else else
perms = ChannelPermissions.All(guildChannel);
perms = ChannelPermissions.All(context.Channel);


if (!perms.Has(ChannelPermission.Value)) if (!perms.Has(ChannelPermission.Value))
return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}")); return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}"));


+ 3
- 3
src/Discord.Net.Commands/CommandParser.cs View File

@@ -14,7 +14,7 @@ namespace Discord.Commands
QuotedParameter QuotedParameter
} }
public static async Task<ParseResult> ParseArgs(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos)
public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos)
{ {
ParameterInfo curParam = null; ParameterInfo curParam = null;
StringBuilder argBuilder = new StringBuilder(input.Length); StringBuilder argBuilder = new StringBuilder(input.Length);
@@ -111,7 +111,7 @@ namespace Discord.Commands
if (curParam == null) if (curParam == null)
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters."); return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters.");


var typeReaderResult = await curParam.Parse(context, argString, services).ConfigureAwait(false);
var typeReaderResult = await curParam.ParseAsync(context, argString, services).ConfigureAwait(false);
if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches) if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches)
return ParseResult.FromError(typeReaderResult); return ParseResult.FromError(typeReaderResult);


@@ -134,7 +134,7 @@ namespace Discord.Commands


if (curParam != null && curParam.IsRemainder) if (curParam != null && curParam.IsRemainder)
{ {
var typeReaderResult = await curParam.Parse(context, argBuilder.ToString(), services).ConfigureAwait(false);
var typeReaderResult = await curParam.ParseAsync(context, argBuilder.ToString(), services).ConfigureAwait(false);
if (!typeReaderResult.IsSuccess) if (!typeReaderResult.IsSuccess)
return ParseResult.FromError(typeReaderResult); return ParseResult.FromError(typeReaderResult);
argList.Add(typeReaderResult); argList.Add(typeReaderResult);


+ 5
- 4
src/Discord.Net.Commands/CommandServiceConfig.cs View File

@@ -2,17 +2,18 @@
{ {
public class CommandServiceConfig public class CommandServiceConfig
{ {
/// <summary> The default RunMode commands should have, if one is not specified on the Command attribute or builder. </summary>
/// <summary> Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or builder. </summary>
public RunMode DefaultRunMode { get; set; } = RunMode.Sync; public RunMode DefaultRunMode { get; set; } = RunMode.Sync;


public char SeparatorChar { get; set; } = ' '; public char SeparatorChar { get; set; } = ' ';
/// <summary> Should commands be case-sensitive? </summary>
/// <summary> Determines whether commands should be case-sensitive. </summary>
public bool CaseSensitiveCommands { get; set; } = false; public bool CaseSensitiveCommands { get; set; } = false;


/// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> /// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary>
public LogSeverity LogLevel { get; set; } = LogSeverity.Info; public LogSeverity LogLevel { get; set; } = LogSeverity.Info;


/// <summary> Gets or sets whether RunMode.Sync commands should push exceptions up to the caller. </summary>
/// <summary> Determines whether RunMode.Sync commands should push exceptions up to the caller. </summary>
public bool ThrowOnError { get; set; } = true; public bool ThrowOnError { get; set; } = true;
} }
}
}

+ 10
- 7
src/Discord.Net.Commands/Info/CommandInfo.cs View File

@@ -78,7 +78,7 @@ namespace Discord.Commands
{ {
foreach (PreconditionAttribute precondition in preconditionGroup) foreach (PreconditionAttribute precondition in preconditionGroup)
{ {
var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false);
var result = await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;
} }
@@ -87,7 +87,7 @@ namespace Discord.Commands
{ {
var results = new List<PreconditionResult>(); var results = new List<PreconditionResult>();
foreach (PreconditionAttribute precondition in preconditionGroup) foreach (PreconditionAttribute precondition in preconditionGroup)
results.Add(await precondition.CheckPermissions(context, this, services).ConfigureAwait(false));
results.Add(await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false));


if (!results.Any(p => p.IsSuccess)) if (!results.Any(p => p.IsSuccess))
return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results); return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results);
@@ -117,7 +117,7 @@ namespace Discord.Commands
return ParseResult.FromError(preconditionResult); return ParseResult.FromError(preconditionResult);


string input = searchResult.Text.Substring(startIndex); string input = searchResult.Text.Substring(startIndex);
return await CommandParser.ParseArgs(this, context, services, input, 0).ConfigureAwait(false);
return await CommandParser.ParseArgsAsync(this, context, services, input, 0).ConfigureAwait(false);
} }


public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
@@ -163,11 +163,11 @@ namespace Discord.Commands
switch (RunMode) switch (RunMode)
{ {
case RunMode.Sync: //Always sync case RunMode.Sync: //Always sync
return await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false);
return await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false);
case RunMode.Async: //Always async case RunMode.Async: //Always async
var t2 = Task.Run(async () => var t2 = Task.Run(async () =>
{ {
await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false);
await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false);
}); });
break; break;
} }
@@ -179,7 +179,7 @@ namespace Discord.Commands
} }
} }


private async Task<IResult> ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider services)
private async Task<IResult> ExecuteAsyncInternalAsync(ICommandContext context, object[] args, IServiceProvider services)
{ {
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
try try
@@ -199,10 +199,13 @@ namespace Discord.Commands
return result; return result;
} }
else else
{
await task.ConfigureAwait(false); await task.ConfigureAwait(false);
var result = ExecuteResult.FromSuccess();
await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false);
}


var executeResult = ExecuteResult.FromSuccess(); var executeResult = ExecuteResult.FromSuccess();
await Module.Service._commandExecutedEvent.InvokeAsync(this, context, executeResult).ConfigureAwait(false);
return executeResult; return executeResult;
} }
catch (Exception ex) catch (Exception ex)


+ 3
- 3
src/Discord.Net.Commands/Info/ParameterInfo.cs View File

@@ -48,7 +48,7 @@ namespace Discord.Commands


foreach (var precondition in Preconditions) foreach (var precondition in Preconditions)
{ {
var result = await precondition.CheckPermissions(context, this, arg, services).ConfigureAwait(false);
var result = await precondition.CheckPermissionsAsync(context, this, arg, services).ConfigureAwait(false);
if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;
} }
@@ -56,10 +56,10 @@ namespace Discord.Commands
return PreconditionResult.FromSuccess(); return PreconditionResult.FromSuccess();
} }


public async Task<TypeReaderResult> Parse(ICommandContext context, string input, IServiceProvider services = null)
public async Task<TypeReaderResult> ParseAsync(ICommandContext context, string input, IServiceProvider services = null)
{ {
services = services ?? EmptyServiceProvider.Instance; services = services ?? EmptyServiceProvider.Instance;
return await _reader.Read(context, input, services).ConfigureAwait(false);
return await _reader.ReadAsync(context, input, services).ConfigureAwait(false);
} }


public override string ToString() => Name; public override string ToString() => Name;


+ 1
- 1
src/Discord.Net.Commands/Readers/ChannelTypeReader.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Commands
internal class ChannelTypeReader<T> : TypeReader internal class ChannelTypeReader<T> : TypeReader
where T : class, IChannel where T : class, IChannel
{ {
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{ {
if (context.Guild != null) if (context.Guild != null)
{ {


+ 1
- 1
src/Discord.Net.Commands/Readers/EnumTypeReader.cs View File

@@ -44,7 +44,7 @@ namespace Discord.Commands
_enumsByValue = byValueBuilder.ToImmutable(); _enumsByValue = byValueBuilder.ToImmutable();
} }


public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{ {
object enumValue; object enumValue;




+ 1
- 1
src/Discord.Net.Commands/Readers/MessageTypeReader.cs View File

@@ -7,7 +7,7 @@ namespace Discord.Commands
internal class MessageTypeReader<T> : TypeReader internal class MessageTypeReader<T> : TypeReader
where T : class, IMessage where T : class, IMessage
{ {
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{ {
ulong id; ulong id;




+ 2
- 2
src/Discord.Net.Commands/Readers/NullableTypeReader.cs View File

@@ -24,11 +24,11 @@ namespace Discord.Commands
_baseTypeReader = baseTypeReader; _baseTypeReader = baseTypeReader;
} }


public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{ {
if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase)) if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase))
return TypeReaderResult.FromSuccess(new T?()); return TypeReaderResult.FromSuccess(new T?());
return await _baseTypeReader.Read(context, input, services); ;
return await _baseTypeReader.ReadAsync(context, input, services);
} }
} }
} }

+ 1
- 1
src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs View File

@@ -30,7 +30,7 @@ namespace Discord.Commands
_score = score; _score = score;
} }


public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{ {
if (_tryParse(input, out T value)) if (_tryParse(input, out T value))
return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score))); return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score)));


+ 1
- 1
src/Discord.Net.Commands/Readers/RoleTypeReader.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Commands
internal class RoleTypeReader<T> : TypeReader internal class RoleTypeReader<T> : TypeReader
where T : class, IRole where T : class, IRole
{ {
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{ {
ulong id; ulong id;




+ 1
- 1
src/Discord.Net.Commands/Readers/TypeReader.cs View File

@@ -5,6 +5,6 @@ namespace Discord.Commands
{ {
public abstract class TypeReader public abstract class TypeReader
{ {
public abstract Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services);
public abstract Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services);
} }
} }

+ 1
- 1
src/Discord.Net.Commands/Readers/UserTypeReader.cs View File

@@ -10,7 +10,7 @@ namespace Discord.Commands
internal class UserTypeReader<T> : TypeReader internal class UserTypeReader<T> : TypeReader
where T : class, IUser where T : class, IUser
{ {
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{ {
var results = new Dictionary<ulong, TypeReaderValue>(); var results = new Dictionary<ulong, TypeReaderValue>();
IReadOnlyCollection<IUser> channelUsers = (await context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way? IReadOnlyCollection<IUser> channelUsers = (await context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way?


+ 1
- 1
src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs View File

@@ -24,7 +24,7 @@ namespace Discord
/// <param name="maxAge"> The time (in seconds) until the invite expires. Set to null to never expire. </param> /// <param name="maxAge"> The time (in seconds) until the invite expires. Set to null to never expire. </param>
/// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param> /// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param>
/// <param name="isTemporary"> If true, a user accepting this invite will be kicked from the guild after closing their client. </param> /// <param name="isTemporary"> If true, a user accepting this invite will be kicked from the guild after closing their client. </param>
Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null);
Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null);
/// <summary> Returns a collection of all invites to this channel. </summary> /// <summary> Returns a collection of all invites to this channel. </summary>
Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null); Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null);




+ 0
- 4
src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs View File

@@ -29,10 +29,6 @@ namespace Discord
CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> Gets a collection of pinned messages in this channel. </summary> /// <summary> Gets a collection of pinned messages in this channel. </summary>
Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(RequestOptions options = null); Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(RequestOptions options = null);
/// <summary> Bulk deletes multiple messages. </summary>
Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null);
/// <summary> Bulk deletes multiple messages. </summary>
Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null);


/// <summary> Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. </summary> /// <summary> Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. </summary>
Task TriggerTypingAsync(RequestOptions options = null); Task TriggerTypingAsync(RequestOptions options = null);


+ 6
- 0
src/Discord.Net.Core/Entities/Channels/ITextChannel.cs View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord namespace Discord
@@ -11,6 +12,11 @@ namespace Discord
/// <summary> Gets the current topic for this text channel. </summary> /// <summary> Gets the current topic for this text channel. </summary>
string Topic { get; } string Topic { get; }


/// <summary> Bulk deletes multiple messages. </summary>
Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null);
/// <summary> Bulk deletes multiple messages. </summary>
Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null);

/// <summary> Modifies this text channel. </summary> /// <summary> Modifies this text channel. </summary>
Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null); Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null);
} }

+ 10
- 0
src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;

namespace Discord
{
public class EmoteProperties
{
public Optional<string> Name { get; set; }
public Optional<IEnumerable<IRole>> Roles { get; set; }
}
}

+ 9
- 0
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -120,5 +120,14 @@ namespace Discord
Task DownloadUsersAsync(); Task DownloadUsersAsync();
/// <summary> 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. </summary> /// <summary> 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. </summary>
Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null);

/// <summary> Gets a specific emote from this guild. </summary>
Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null);
/// <summary> Creates a new emote in this guild. </summary>
Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null);
/// <summary> Modifies an existing emote in this guild. </summary>
Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null);
/// <summary> Deletes an existing emote from this guild. </summary>
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null);
} }
} }

+ 29
- 33
src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs View File

@@ -1,40 +1,36 @@
namespace Discord
using System;

namespace Discord
{ {
public enum ChannelPermission : byte
[FlagsAttribute]
public enum ChannelPermission : ulong
{ {
//General
CreateInstantInvite = 0,
//KickMembers = 1,
//BanMembers = 2,
//Administrator = 3,
ManageChannel = 4,
//ManageGuild = 5,
// General
CreateInstantInvite = 0x00_00_00_01,
ManageChannels = 0x00_00_00_10,


//Text
AddReactions = 6,
ReadMessages = 10,
SendMessages = 11,
SendTTSMessages = 12,
ManageMessages = 13,
EmbedLinks = 14,
AttachFiles = 15,
ReadMessageHistory = 16,
MentionEveryone = 17,
UseExternalEmojis = 18,
// Text
AddReactions = 0x00_00_00_40,
ReadMessages = 0x00_00_04_00,
SendMessages = 0x00_00_08_00,
SendTTSMessages = 0x00_00_10_00,
ManageMessages = 0x00_00_20_00,
EmbedLinks = 0x00_00_40_00,
AttachFiles = 0x00_00_80_00,
ReadMessageHistory = 0x00_01_00_00,
MentionEveryone = 0x00_02_00_00,
UseExternalEmojis = 0x00_04_00_00,


//Voice
Connect = 20,
Speak = 21,
MuteMembers = 22,
DeafenMembers = 23,
MoveMembers = 24,
UseVAD = 25,
// Voice
Connect = 0x00_10_00_00,
Speak = 0x00_20_00_00,
MuteMembers = 0x00_40_00_00,
DeafenMembers = 0x00_80_00_00,
MoveMembers = 0x01_00_00_00,
UseVAD = 0x02_00_00_00,


//General2
//ChangeNickname = 26,
//ManageNicknames = 27,
ManagePermissions = 28,
ManageWebhooks = 29,
//ManageEmojis = 30
// More General
ManageRoles = 0x10_00_00_00,
ManageWebhooks = 0x20_00_00_00,
} }
} }

+ 15
- 15
src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs View File

@@ -10,7 +10,7 @@ namespace Discord
/// <summary> Gets a blank ChannelPermissions that grants no permissions. </summary> /// <summary> Gets a blank ChannelPermissions that grants no permissions. </summary>
public static readonly ChannelPermissions None = new ChannelPermissions(); public static readonly ChannelPermissions None = new ChannelPermissions();
/// <summary> Gets a ChannelPermissions that grants all permissions for text channels. </summary> /// <summary> Gets a ChannelPermissions that grants all permissions for text channels. </summary>
public static readonly ChannelPermissions Text = new ChannelPermissions(0b00100_0000000_1111111110001_010001);
public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001);
/// <summary> Gets a ChannelPermissions that grants all permissions for voice channels. </summary> /// <summary> Gets a ChannelPermissions that grants all permissions for voice channels. </summary>
public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001);
/// <summary> Gets a ChannelPermissions that grants all permissions for direct message channels. </summary> /// <summary> Gets a ChannelPermissions that grants all permissions for direct message channels. </summary>
@@ -36,7 +36,7 @@ namespace Discord
/// <summary> If True, a user may create invites. </summary> /// <summary> If True, a user may create invites. </summary>
public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite);
/// <summary> If True, a user may create, delete and modify this channel. </summary> /// <summary> If True, a user may create, delete and modify this channel. </summary>
public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannel);
public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannels);


/// <summary> If true, a user may add reactions. </summary> /// <summary> If true, a user may add reactions. </summary>
public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions);
@@ -72,8 +72,8 @@ namespace Discord
/// <summary> If True, a user may use voice-activity-detection rather than push-to-talk. </summary> /// <summary> If True, a user may use voice-activity-detection rather than push-to-talk. </summary>
public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD);


/// <summary> If True, a user may adjust permissions. This also implictly grants all other permissions. </summary>
public bool ManagePermissions => Permissions.GetValue(RawValue, ChannelPermission.ManagePermissions);
/// <summary> If True, a user may adjust role permissions. This also implictly grants all other permissions. </summary>
public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles);
/// <summary> If True, a user may edit the webhooks for this channel. </summary> /// <summary> If True, a user may edit the webhooks for this channel. </summary>
public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks); public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks);


@@ -85,12 +85,12 @@ namespace Discord
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null,
bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, 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? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null,
bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null, bool? manageWebhooks = null)
bool? moveMembers = null, bool? useVoiceActivation = null, bool? manageRoles = null, bool? manageWebhooks = null)
{ {
ulong value = initialValue; ulong value = initialValue;


Permissions.SetValue(ref value, createInstantInvite, ChannelPermission.CreateInstantInvite); Permissions.SetValue(ref value, createInstantInvite, ChannelPermission.CreateInstantInvite);
Permissions.SetValue(ref value, manageChannel, ChannelPermission.ManageChannel);
Permissions.SetValue(ref value, manageChannel, ChannelPermission.ManageChannels);
Permissions.SetValue(ref value, addReactions, ChannelPermission.AddReactions); Permissions.SetValue(ref value, addReactions, ChannelPermission.AddReactions);
Permissions.SetValue(ref value, readMessages, ChannelPermission.ReadMessages); Permissions.SetValue(ref value, readMessages, ChannelPermission.ReadMessages);
Permissions.SetValue(ref value, sendMessages, ChannelPermission.SendMessages); Permissions.SetValue(ref value, sendMessages, ChannelPermission.SendMessages);
@@ -107,7 +107,7 @@ namespace Discord
Permissions.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers); Permissions.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers);
Permissions.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers); Permissions.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers);
Permissions.SetValue(ref value, useVoiceActivation, ChannelPermission.UseVAD); Permissions.SetValue(ref value, useVoiceActivation, ChannelPermission.UseVAD);
Permissions.SetValue(ref value, managePermissions, ChannelPermission.ManagePermissions);
Permissions.SetValue(ref value, manageRoles, ChannelPermission.ManageRoles);
Permissions.SetValue(ref value, manageWebhooks, ChannelPermission.ManageWebhooks); Permissions.SetValue(ref value, manageWebhooks, ChannelPermission.ManageWebhooks);


RawValue = value; RawValue = value;
@@ -119,10 +119,10 @@ namespace Discord
bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false,
bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, 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 useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false,
bool moveMembers = false, bool useVoiceActivation = false, bool managePermissions = false, bool manageWebhooks = false)
bool moveMembers = false, bool useVoiceActivation = false, bool manageRoles = false, bool manageWebhooks = false)
: this(0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, : this(0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages,
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect,
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks)
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks)
{ } { }


/// <summary> Creates a new ChannelPermissions from this one, changing the provided non-null permissions. </summary> /// <summary> Creates a new ChannelPermissions from this one, changing the provided non-null permissions. </summary>
@@ -131,21 +131,21 @@ namespace Discord
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null,
bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null,
bool useExternalEmojis = false, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool useExternalEmojis = false, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null,
bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null, bool? manageWebhooks = null)
bool? moveMembers = null, bool? useVoiceActivation = null, bool? manageRoles = null, bool? manageWebhooks = null)
=> new ChannelPermissions(RawValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages,
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect,
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks);
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks);


public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission);


public List<ChannelPermission> ToList() public List<ChannelPermission> ToList()
{ {
var perms = new List<ChannelPermission>(); var perms = new List<ChannelPermission>();
ulong x = 1;
for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1)
for (byte i = 0; i < Permissions.MaxBits; i++)
{ {
if ((RawValue & x) != 0)
perms.Add((ChannelPermission)i);
ulong flag = ((ulong)1 << i);
if ((RawValue & flag) != 0)
perms.Add((ChannelPermission)flag);
} }
return perms; return perms;
} }


+ 37
- 33
src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs View File

@@ -1,40 +1,44 @@
namespace Discord
using System;

namespace Discord
{ {
public enum GuildPermission : byte
[FlagsAttribute]
public enum GuildPermission : ulong
{ {
//General
CreateInstantInvite = 0,
KickMembers = 1,
BanMembers = 2,
Administrator = 3,
ManageChannels = 4,
ManageGuild = 5,
// General
CreateInstantInvite = 0x00_00_00_01,
KickMembers = 0x00_00_00_02,
BanMembers = 0x00_00_00_04,
Administrator = 0x00_00_00_08,
ManageChannels = 0x00_00_00_10,
ManageGuild = 0x00_00_00_20,


//Text
AddReactions = 6,
ReadMessages = 10,
SendMessages = 11,
SendTTSMessages = 12,
ManageMessages = 13,
EmbedLinks = 14,
AttachFiles = 15,
ReadMessageHistory = 16,
MentionEveryone = 17,
UseExternalEmojis = 18,
// Text
AddReactions = 0x00_00_00_40,
ViewAuditLog = 0x00_00_00_80,
ReadMessages = 0x00_00_04_00,
SendMessages = 0x00_00_08_00,
SendTTSMessages = 0x00_00_10_00,
ManageMessages = 0x00_00_20_00,
EmbedLinks = 0x00_00_40_00,
AttachFiles = 0x00_00_80_00,
ReadMessageHistory = 0x00_01_00_00,
MentionEveryone = 0x00_02_00_00,
UseExternalEmojis = 0x00_04_00_00,


//Voice
Connect = 20,
Speak = 21,
MuteMembers = 22,
DeafenMembers = 23,
MoveMembers = 24,
UseVAD = 25,
// Voice
Connect = 0x00_10_00_00,
Speak = 0x00_20_00_00,
MuteMembers = 0x00_40_00_00,
DeafenMembers = 0x00_80_00_00,
MoveMembers = 0x01_00_00_00,
UseVAD = 0x02_00_00_00,


//General2
ChangeNickname = 26,
ManageNicknames = 27,
ManageRoles = 28,
ManageWebhooks = 29,
ManageEmojis = 30
// General 2
ChangeNickname = 0x04_00_00_00,
ManageNicknames = 0x08_00_00_00,
ManageRoles = 0x10_00_00_00,
ManageWebhooks = 0x20_00_00_00,
ManageEmojis = 0x40_00_00_00
} }
} }

+ 40
- 28
src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs View File

@@ -11,7 +11,7 @@ namespace Discord
/// <summary> Gets a GuildPermissions that grants all guild permissions for webhook users. </summary> /// <summary> Gets a GuildPermissions that grants all guild permissions for webhook users. </summary>
public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000);
/// <summary> Gets a GuildPermissions that grants all guild permissions. </summary> /// <summary> Gets a GuildPermissions that grants all guild permissions. </summary>
public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110001_111111);
public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111110011_111111);


/// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> /// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary>
public ulong RawValue { get; } public ulong RawValue { get; }
@@ -28,9 +28,12 @@ namespace Discord
public bool ManageChannels => Permissions.GetValue(RawValue, GuildPermission.ManageChannels); public bool ManageChannels => Permissions.GetValue(RawValue, GuildPermission.ManageChannels);
/// <summary> If True, a user may adjust guild properties. </summary> /// <summary> If True, a user may adjust guild properties. </summary>
public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild); public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild);
/// <summary> If true, a user may add reactions. </summary> /// <summary> If true, a user may add reactions. </summary>
public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions);
/// <summary> If true, a user may view the audit log. </summary>
public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog);

/// <summary> If True, a user may join channels. </summary> /// <summary> If True, a user may join channels. </summary>
public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages);
/// <summary> If True, a user may send messages. </summary> /// <summary> If True, a user may send messages. </summary>
@@ -77,13 +80,13 @@ namespace Discord
/// <summary> Creates a new GuildPermissions with the provided packed value. </summary> /// <summary> Creates a new GuildPermissions with the provided packed value. </summary>
public GuildPermissions(ulong rawValue) { RawValue = rawValue; } public GuildPermissions(ulong rawValue) { RawValue = rawValue; }


private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null,
bool? banMembers = null, bool? administrator = null, bool? manageChannel = null, bool? manageGuild = null,
bool? addReactions = null,
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null,
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,
private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null,
bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null,
bool? addReactions = null, bool? viewAuditLog = null,
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null,
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? manageWebhooks = null, bool? manageEmojis = null) bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null)
{ {
ulong value = initialValue; ulong value = initialValue;
@@ -92,9 +95,10 @@ namespace Discord
Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers); Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers);
Permissions.SetValue(ref value, kickMembers, GuildPermission.KickMembers); Permissions.SetValue(ref value, kickMembers, GuildPermission.KickMembers);
Permissions.SetValue(ref value, administrator, GuildPermission.Administrator); Permissions.SetValue(ref value, administrator, GuildPermission.Administrator);
Permissions.SetValue(ref value, manageChannel, GuildPermission.ManageChannels);
Permissions.SetValue(ref value, manageChannels, GuildPermission.ManageChannels);
Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild); Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild);
Permissions.SetValue(ref value, addReactions, GuildPermission.AddReactions); Permissions.SetValue(ref value, addReactions, GuildPermission.AddReactions);
Permissions.SetValue(ref value, viewAuditLog, GuildPermission.ViewAuditLog);
Permissions.SetValue(ref value, readMessages, GuildPermission.ReadMessages); Permissions.SetValue(ref value, readMessages, GuildPermission.ReadMessages);
Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages); Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages);
Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages);
@@ -103,7 +107,7 @@ namespace Discord
Permissions.SetValue(ref value, attachFiles, GuildPermission.AttachFiles); Permissions.SetValue(ref value, attachFiles, GuildPermission.AttachFiles);
Permissions.SetValue(ref value, readMessageHistory, GuildPermission.ReadMessageHistory); Permissions.SetValue(ref value, readMessageHistory, GuildPermission.ReadMessageHistory);
Permissions.SetValue(ref value, mentionEveryone, GuildPermission.MentionEveryone); Permissions.SetValue(ref value, mentionEveryone, GuildPermission.MentionEveryone);
Permissions.SetValue(ref value, userExternalEmojis, GuildPermission.UseExternalEmojis);
Permissions.SetValue(ref value, useExternalEmojis, GuildPermission.UseExternalEmojis);
Permissions.SetValue(ref value, connect, GuildPermission.Connect); Permissions.SetValue(ref value, connect, GuildPermission.Connect);
Permissions.SetValue(ref value, speak, GuildPermission.Speak); Permissions.SetValue(ref value, speak, GuildPermission.Speak);
Permissions.SetValue(ref value, muteMembers, GuildPermission.MuteMembers); Permissions.SetValue(ref value, muteMembers, GuildPermission.MuteMembers);
@@ -120,42 +124,50 @@ namespace Discord
} }


/// <summary> Creates a new GuildPermissions with the provided permissions. </summary> /// <summary> Creates a new GuildPermissions with the provided permissions. </summary>
public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false,
public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false,
bool banMembers = false, bool administrator = false, bool manageChannels = false, bool manageGuild = false, bool banMembers = false, bool administrator = false, bool manageChannels = false, bool manageGuild = false,
bool addReactions = false,
bool addReactions = false, bool viewAuditLog = false,
bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false,
bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, 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 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 moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false,
bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false)
: this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions,
readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect,
manageWebhooks, manageEmojis) { }
: this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, kickMembers: kickMembers, banMembers: banMembers,
administrator: administrator, manageChannels: manageChannels, manageGuild: manageGuild, addReactions: addReactions,
viewAuditLog: viewAuditLog, readMessages: readMessages, sendMessages: sendMessages, sendTTSMessages: sendTTSMessages,
manageMessages: manageMessages, embedLinks: embedLinks, attachFiles: attachFiles, readMessageHistory: readMessageHistory,
mentionEveryone: mentionEveryone, useExternalEmojis: useExternalEmojis, connect: connect, speak: speak, muteMembers: muteMembers,
deafenMembers: deafenMembers, moveMembers: moveMembers, useVoiceActivation: useVoiceActivation, changeNickname: changeNickname,
manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, manageEmojis: manageEmojis)
{ }


/// <summary> Creates a new GuildPermissions from this one, changing the provided non-null permissions. </summary> /// <summary> Creates a new GuildPermissions from this one, changing the provided non-null permissions. </summary>
public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null,
public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null,
bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null,
bool? addReactions = null,
bool? addReactions = null, bool? viewAuditLog = null,
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null,
bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, 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? 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? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null,
bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null)
=> new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions,
readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect,
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles,
manageWebhooks, manageEmojis);
=> new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions,
viewAuditLog, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles,
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers,
useVoiceActivation, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis);


public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission); public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission);


public List<GuildPermission> ToList() public List<GuildPermission> ToList()
{ {
var perms = new List<GuildPermission>(); var perms = new List<GuildPermission>();
ulong x = 1;
for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1)

// bitwise operations on raw value
// each of the GuildPermissions increments by 2^i from 0 to MaxBits
for (byte i = 0; i < Permissions.MaxBits; i++)
{ {
if ((RawValue & x) != 0)
perms.Add((GuildPermission)i);
ulong flag = ((ulong)1 << i);
if ((RawValue & flag) != 0)
perms.Add((GuildPermission)flag);
} }
return perms; return perms;
} }


+ 19
- 18
src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs View File

@@ -23,7 +23,7 @@ namespace Discord
/// <summary> If Allowed, a user may create invites. </summary> /// <summary> If Allowed, a user may create invites. </summary>
public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite);
/// <summary> If Allowed, a user may create, delete and modify this channel. </summary> /// <summary> If Allowed, a user may create, delete and modify this channel. </summary>
public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannel);
public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannels);
/// <summary> If Allowed, a user may add reactions. </summary> /// <summary> If Allowed, a user may add reactions. </summary>
public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions);
/// <summary> If Allowed, a user may join channels. </summary> /// <summary> If Allowed, a user may join channels. </summary>
@@ -58,8 +58,8 @@ namespace Discord
/// <summary> If Allowed, a user may use voice-activity-detection rather than push-to-talk. </summary> /// <summary> If Allowed, a user may use voice-activity-detection rather than push-to-talk. </summary>
public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD);


/// <summary> If Allowed, a user may adjust permissions. This also implictly grants all other permissions. </summary>
public PermValue ManagePermissions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManagePermissions);
/// <summary> If Allowed, a user may adjust role permissions. This also implictly grants all other permissions. </summary>
public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles);
/// <summary> If True, a user may edit the webhooks for this channel. </summary> /// <summary> If True, a user may edit the webhooks for this channel. </summary>
public PermValue ManageWebhooks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks); public PermValue ManageWebhooks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks);


@@ -75,11 +75,11 @@ namespace Discord
PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null,
PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null,
PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null,
PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? managePermissions = null,
PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null,
PermValue? manageWebhooks = null) PermValue? manageWebhooks = null)
{ {
Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite);
Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannel);
Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannels);
Permissions.SetValue(ref allowValue, ref denyValue, addReactions, ChannelPermission.AddReactions); Permissions.SetValue(ref allowValue, ref denyValue, addReactions, ChannelPermission.AddReactions);
Permissions.SetValue(ref allowValue, ref denyValue, readMessages, ChannelPermission.ReadMessages); Permissions.SetValue(ref allowValue, ref denyValue, readMessages, ChannelPermission.ReadMessages);
Permissions.SetValue(ref allowValue, ref denyValue, sendMessages, ChannelPermission.SendMessages); Permissions.SetValue(ref allowValue, ref denyValue, sendMessages, ChannelPermission.SendMessages);
@@ -96,7 +96,7 @@ namespace Discord
Permissions.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers); Permissions.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers);
Permissions.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers); Permissions.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers);
Permissions.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD); Permissions.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD);
Permissions.SetValue(ref allowValue, ref denyValue, managePermissions, ChannelPermission.ManagePermissions);
Permissions.SetValue(ref allowValue, ref denyValue, manageRoles, ChannelPermission.ManageRoles);
Permissions.SetValue(ref allowValue, ref denyValue, manageWebhooks, ChannelPermission.ManageWebhooks); Permissions.SetValue(ref allowValue, ref denyValue, manageWebhooks, ChannelPermission.ManageWebhooks);


AllowValue = allowValue; AllowValue = allowValue;
@@ -109,10 +109,10 @@ namespace Discord
PermValue readMessages = PermValue.Inherit, PermValue sendMessages = PermValue.Inherit, PermValue sendTTSMessages = PermValue.Inherit, PermValue manageMessages = PermValue.Inherit, PermValue readMessages = PermValue.Inherit, PermValue sendMessages = PermValue.Inherit, PermValue sendTTSMessages = PermValue.Inherit, PermValue manageMessages = PermValue.Inherit,
PermValue embedLinks = PermValue.Inherit, PermValue attachFiles = PermValue.Inherit, PermValue readMessageHistory = PermValue.Inherit, PermValue mentionEveryone = PermValue.Inherit, PermValue embedLinks = PermValue.Inherit, PermValue attachFiles = PermValue.Inherit, PermValue readMessageHistory = PermValue.Inherit, PermValue mentionEveryone = PermValue.Inherit,
PermValue useExternalEmojis = PermValue.Inherit, PermValue connect = PermValue.Inherit, PermValue speak = PermValue.Inherit, PermValue muteMembers = PermValue.Inherit, PermValue deafenMembers = PermValue.Inherit, PermValue useExternalEmojis = PermValue.Inherit, PermValue connect = PermValue.Inherit, PermValue speak = PermValue.Inherit, PermValue muteMembers = PermValue.Inherit, PermValue deafenMembers = PermValue.Inherit,
PermValue moveMembers = PermValue.Inherit, PermValue useVoiceActivation = PermValue.Inherit, PermValue managePermissions = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit)
PermValue moveMembers = PermValue.Inherit, PermValue useVoiceActivation = PermValue.Inherit, PermValue manageRoles = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit)
: this(0, 0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, : this(0, 0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages,
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers,
moveMembers, useVoiceActivation, managePermissions, manageWebhooks) { }
moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { }


/// <summary> Creates a new OverwritePermissions from this one, changing the provided non-null permissions. </summary> /// <summary> Creates a new OverwritePermissions from this one, changing the provided non-null permissions. </summary>
public OverwritePermissions Modify(PermValue? createInstantInvite = null, PermValue? manageChannel = null, public OverwritePermissions Modify(PermValue? createInstantInvite = null, PermValue? manageChannel = null,
@@ -120,30 +120,31 @@ namespace Discord
PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null,
PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null,
PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null,
PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? managePermissions = null, PermValue? manageWebhooks = null)
PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null, PermValue? manageWebhooks = null)
=> new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages,
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers,
moveMembers, useVoiceActivation, managePermissions, manageWebhooks);
moveMembers, useVoiceActivation, manageRoles, manageWebhooks);


public List<ChannelPermission> ToAllowList() public List<ChannelPermission> ToAllowList()
{ {
var perms = new List<ChannelPermission>(); var perms = new List<ChannelPermission>();
ulong x = 1;
for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1)
for (byte i = 0; i < Permissions.MaxBits; i++)
{ {
if ((AllowValue & x) != 0)
perms.Add((ChannelPermission)i);
// first operand must be long or ulong to shift >31 bits
ulong flag = ((ulong)1 << i);
if ((AllowValue & flag) != 0)
perms.Add((ChannelPermission)flag);
} }
return perms; return perms;
} }
public List<ChannelPermission> ToDenyList() public List<ChannelPermission> ToDenyList()
{ {
var perms = new List<ChannelPermission>(); var perms = new List<ChannelPermission>();
ulong x = 1;
for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1)
for (byte i = 0; i < Permissions.MaxBits; i++)
{ {
if ((DenyValue & x) != 0)
perms.Add((ChannelPermission)i);
ulong flag = ((ulong)1 << i);
if ((DenyValue & flag) != 0)
perms.Add((ChannelPermission)flag);
} }
return perms; return perms;
} }


+ 4
- 1
src/Discord.Net.Core/TokenType.cs View File

@@ -1,7 +1,10 @@
namespace Discord
using System;

namespace Discord
{ {
public enum TokenType public enum TokenType
{ {
[Obsolete("User logins are being deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827")]
User, User,
Bearer, Bearer,
Bot, Bot,


+ 1
- 1
src/Discord.Net.Core/Utils/Optional.cs View File

@@ -10,7 +10,7 @@ namespace Discord
public static Optional<T> Unspecified => default(Optional<T>); public static Optional<T> Unspecified => default(Optional<T>);
private readonly T _value; private readonly T _value;


/// <summary> Gets the value for this paramter. </summary>
/// <summary> Gets the value for this parameter. </summary>
public T Value public T Value
{ {
get get


+ 33
- 33
src/Discord.Net.Core/Utils/Permissions.cs View File

@@ -7,84 +7,84 @@ namespace Discord
public const int MaxBits = 53; public const int MaxBits = 53;


[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission bit)
=> GetValue(allow, deny, (byte)bit);
public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission flag)
=> GetValue(allow, deny, (ulong)flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PermValue GetValue(ulong allow, ulong deny, GuildPermission bit)
=> GetValue(allow, deny, (byte)bit);
public static PermValue GetValue(ulong allow, ulong deny, GuildPermission flag)
=> GetValue(allow, deny, (ulong)flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PermValue GetValue(ulong allow, ulong deny, byte bit)
public static PermValue GetValue(ulong allow, ulong deny, ulong flag)
{ {
if (HasBit(allow, bit))
if (HasFlag(allow, flag))
return PermValue.Allow; return PermValue.Allow;
else if (HasBit(deny, bit))
else if (HasFlag(deny, flag))
return PermValue.Deny; return PermValue.Deny;
else else
return PermValue.Inherit; return PermValue.Inherit;
} }


[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetValue(ulong value, ChannelPermission bit)
=> GetValue(value, (byte)bit);
public static bool GetValue(ulong value, ChannelPermission flag)
=> GetValue(value, (ulong)flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetValue(ulong value, GuildPermission bit)
=> GetValue(value, (byte)bit);
public static bool GetValue(ulong value, GuildPermission flag)
=> GetValue(value, (ulong)flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetValue(ulong value, byte bit) => HasBit(value, bit);
public static bool GetValue(ulong value, ulong flag) => HasFlag(value, flag);


[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission bit)
=> SetValue(ref rawValue, value, (byte)bit);
public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission flag)
=> SetValue(ref rawValue, value, (ulong)flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetValue(ref ulong rawValue, bool? value, GuildPermission bit)
=> SetValue(ref rawValue, value, (byte)bit);
public static void SetValue(ref ulong rawValue, bool? value, GuildPermission flag)
=> SetValue(ref rawValue, value, (ulong)flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetValue(ref ulong rawValue, bool? value, byte bit)
public static void SetValue(ref ulong rawValue, bool? value, ulong flag)
{ {
if (value.HasValue) if (value.HasValue)
{ {
if (value == true) if (value == true)
SetBit(ref rawValue, bit);
SetFlag(ref rawValue, flag);
else else
UnsetBit(ref rawValue, bit);
UnsetFlag(ref rawValue, flag);
} }
} }


[MethodImpl(MethodImplOptions.AggressiveInlining)] [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);
public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission flag)
=> SetValue(ref allow, ref deny, value, (ulong)flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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);
public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission flag)
=> SetValue(ref allow, ref deny, value, (ulong)flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, byte bit)
public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ulong flag)
{ {
if (value.HasValue) if (value.HasValue)
{ {
switch (value) switch (value)
{ {
case PermValue.Allow: case PermValue.Allow:
SetBit(ref allow, bit);
UnsetBit(ref deny, bit);
SetFlag(ref allow, flag);
UnsetFlag(ref deny, flag);
break; break;
case PermValue.Deny: case PermValue.Deny:
UnsetBit(ref allow, bit);
SetBit(ref deny, bit);
UnsetFlag(ref allow, flag);
SetFlag(ref deny, flag);
break; break;
default: default:
UnsetBit(ref allow, bit);
UnsetBit(ref deny, bit);
UnsetFlag(ref allow, flag);
UnsetFlag(ref deny, flag);
break; break;
} }
} }
} }


[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool HasBit(ulong value, byte bit) => (value & (1U << bit)) != 0;
private static bool HasFlag(ulong value, ulong flag) => (value & flag) != 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetBit(ref ulong value, byte bit) => value |= (1U << bit);
public static void SetFlag(ref ulong value, ulong flag) => value |= flag;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit);
public static void UnsetFlag(ref ulong value, ulong flag) => value &= ~flag;


public static ChannelPermissions ToChannelPerms(IGuildChannel channel, ulong guildPermissions) public static ChannelPermissions ToChannelPerms(IGuildChannel channel, ulong guildPermissions)
=> new ChannelPermissions(guildPermissions & ChannelPermissions.All(channel).RawValue); => new ChannelPermissions(guildPermissions & ChannelPermissions.All(channel).RawValue);


+ 2
- 1
src/Discord.Net.Core/Utils/Preconditions.cs View File

@@ -188,7 +188,8 @@ namespace Discord
var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14)));
for (var i = 0; i < collection.Length; i++) for (var i = 0; i < collection.Length; i++)
{ {
if (collection[i] <= minimum)
if (collection[i] == 0) continue;
if (collection[i] <= minimum)
throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old.");
} }
} }


+ 16
- 0
src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs View File

@@ -0,0 +1,16 @@
#pragma warning disable CS1591
using Newtonsoft.Json;

namespace Discord.API.Rest
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class CreateGuildEmoteParams
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("image")]
public Image Image { get; set; }
[JsonProperty("roles")]
public Optional<ulong[]> RoleIds { get; set; }
}
}

+ 14
- 0
src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs View File

@@ -0,0 +1,14 @@
#pragma warning disable CS1591
using Newtonsoft.Json;

namespace Discord.API.Rest
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class ModifyGuildEmoteParams
{
[JsonProperty("name")]
public Optional<string> Name { get; set; }
[JsonProperty("roles")]
public Optional<ulong[]> RoleIds { get; set; }
}
}

+ 8
- 4
src/Discord.Net.Rest/BaseDiscordClient.cs View File

@@ -85,7 +85,8 @@ namespace Discord.Rest


await _loggedInEvent.InvokeAsync().ConfigureAwait(false); await _loggedInEvent.InvokeAsync().ConfigureAwait(false);
} }
internal virtual Task OnLoginAsync(TokenType tokenType, string token) { return Task.Delay(0); }
internal virtual Task OnLoginAsync(TokenType tokenType, string token)
=> Task.Delay(0);


/// <inheritdoc /> /// <inheritdoc />
public async Task LogoutAsync() public async Task LogoutAsync()
@@ -110,7 +111,8 @@ namespace Discord.Rest


await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); await _loggedOutEvent.InvokeAsync().ConfigureAwait(false);
} }
internal virtual Task OnLogoutAsync() { return Task.Delay(0); }
internal virtual Task OnLogoutAsync()
=> Task.Delay(0);


internal virtual void Dispose(bool disposing) internal virtual void Dispose(bool disposing)
{ {
@@ -127,7 +129,8 @@ namespace Discord.Rest
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;
ISelfUser IDiscordClient.CurrentUser => CurrentUser; ISelfUser IDiscordClient.CurrentUser => CurrentUser;


Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) { throw new NotSupportedException(); }
Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
=> throw new NotSupportedException();


Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IChannel>(null); => Task.FromResult<IChannel>(null);
@@ -148,7 +151,8 @@ namespace Discord.Rest
=> Task.FromResult<IGuild>(null); => Task.FromResult<IGuild>(null);
Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IGuild>>(ImmutableArray.Create<IGuild>()); => Task.FromResult<IReadOnlyCollection<IGuild>>(ImmutableArray.Create<IGuild>());
Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) { throw new NotSupportedException(); }
Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options)
=> throw new NotSupportedException();


Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(null); => Task.FromResult<IUser>(null);


+ 45
- 0
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable CS0618
using Discord.API.Rest; using Discord.API.Rest;
using Discord.Net; using Discord.Net;
using Discord.Net.Converters; using Discord.Net.Converters;
@@ -1065,6 +1066,50 @@ namespace Discord.API
return await SendJsonAsync<IReadOnlyCollection<Role>>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); return await SendJsonAsync<IReadOnlyCollection<Role>>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false);
} }


//Guild emoji
public async Task<Emoji> GetGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(emoteId, 0, nameof(emoteId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
return await SendAsync<Emoji>("GET", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options);
}

public async Task<Emoji> CreateGuildEmoteAsync(ulong guildId, Rest.CreateGuildEmoteParams args, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotNull(args, nameof(args));
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
Preconditions.NotNull(args.Image.Stream, nameof(args.Image));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
return await SendJsonAsync<Emoji>("POST", () => $"guilds/{guildId}/emojis", args, ids, options: options);
}

public async Task<Emoji> ModifyGuildEmoteAsync(ulong guildId, ulong emoteId, ModifyGuildEmoteParams args, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(emoteId, 0, nameof(emoteId));
Preconditions.NotNull(args, nameof(args));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
return await SendJsonAsync<Emoji>("PATCH", () => $"guilds/{guildId}/emojis/{emoteId}", args, ids, options: options);
}

public async Task DeleteGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(emoteId, 0, nameof(emoteId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
await SendAsync("DELETE", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options);
}

//Users //Users
public async Task<User> GetUserAsync(ulong userId, RequestOptions options = null) public async Task<User> GetUserAsync(ulong userId, RequestOptions options = null)
{ {


+ 1
- 1
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -184,7 +184,7 @@ namespace Discord.Rest
return RestUserMessage.Create(client, channel, client.CurrentUser, model); return RestUserMessage.Create(client, channel, client.CurrentUser, model);
} }


public static async Task DeleteMessagesAsync(IMessageChannel channel, BaseDiscordClient client,
public static async Task DeleteMessagesAsync(ITextChannel channel, BaseDiscordClient client,
IEnumerable<ulong> messageIds, RequestOptions options) IEnumerable<ulong> messageIds, RequestOptions options)
{ {
var msgs = messageIds.ToArray(); var msgs = messageIds.ToArray();


+ 2
- 2
src/Discord.Net.Rest/Entities/Channels/RestChannel.cs View File

@@ -48,8 +48,8 @@ namespace Discord.Rest
string IChannel.Name => null; string IChannel.Name => null;


Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(null); //Overriden
=> Task.FromResult<IUser>(null); //Overridden
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overriden
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden
} }
} }

+ 0
- 5
src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs View File

@@ -72,11 +72,6 @@ namespace Discord.Rest
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);
public IDisposable EnterTypingState(RequestOptions options = null) public IDisposable EnterTypingState(RequestOptions options = null)


+ 0
- 5
src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs View File

@@ -85,11 +85,6 @@ namespace Discord.Rest
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);
public IDisposable EnterTypingState(RequestOptions options = null) public IDisposable EnterTypingState(RequestOptions options = null)


+ 5
- 5
src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs View File

@@ -123,7 +123,7 @@ namespace Discord.Rest


public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
=> await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);


public override string ToString() => Name; public override string ToString() => Name;
@@ -158,14 +158,14 @@ namespace Discord.Rest
=> await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false);


IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overriden //Overriden in Text/Voice
=> AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overridden //Overridden in Text/Voice
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuildUser>(null); //Overriden in Text/Voice
=> Task.FromResult<IGuildUser>(null); //Overridden in Text/Voice


//IChannel //IChannel
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overriden in Text/Voice
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden in Text/Voice
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(null); //Overriden in Text/Voice
=> Task.FromResult<IUser>(null); //Overridden in Text/Voice
} }
} }

+ 0
- 5
src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs View File

@@ -42,11 +42,6 @@ namespace Discord.Rest
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) public Task<RestUserMessage> 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, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);
public IDisposable EnterTypingState(RequestOptions options = null) public IDisposable EnterTypingState(RequestOptions options = null)


+ 41
- 0
src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs View File

@@ -262,5 +262,46 @@ namespace Discord.Rest
model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args, options).ConfigureAwait(false); model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args, options).ConfigureAwait(false);
return model.Pruned; return model.Pruned;
} }

//Emotes
public static async Task<GuildEmote> GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options)
{
var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options);
return emote.ToEntity();
}
public static async Task<GuildEmote> CreateEmoteAsync(IGuild guild, BaseDiscordClient client, string name, Image image, Optional<IEnumerable<IRole>> roles,
RequestOptions options)
{
var apiargs = new CreateGuildEmoteParams
{
Name = name,
Image = image.ToModel()
};
if (roles.IsSpecified)
apiargs.RoleIds = roles.Value?.Select(xr => xr.Id)?.ToArray();

var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options);
return emote.ToEntity();
}
public static async Task<GuildEmote> ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action<EmoteProperties> func,
RequestOptions options)
{
if (func == null) throw new ArgumentNullException(nameof(func));

var props = new EmoteProperties();
func(props);

var apiargs = new ModifyGuildEmoteParams
{
Name = props.Name
};
if (props.Roles.IsSpecified)
apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id)?.ToArray();

var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options);
return emote.ToEntity();
}
public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options)
=> client.ApiClient.DeleteGuildEmoteAsync(guild.Id, id, options);
} }
} }

+ 10
- 0
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs View File

@@ -267,6 +267,16 @@ namespace Discord.Rest
public override string ToString() => Name; public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id})"; private string DebuggerDisplay => $"{Name} ({Id})";


//Emotes
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetEmoteAsync(this, Discord, id, options);
public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null)
=> GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options);
public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null)
=> GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options);
public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null)
=> GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options);

//IGuild //IGuild
bool IGuild.Available => Available; bool IGuild.Available => Available;
IAudioClient IGuild.AudioClient => null; IAudioClient IGuild.AudioClient => null;


+ 35
- 0
src/Discord.Net.Rest/Extensions/EmbedBuilderExtensions.cs View File

@@ -1,3 +1,5 @@
using System;

namespace Discord namespace Discord
{ {
public static class EmbedBuilderExtensions public static class EmbedBuilderExtensions
@@ -19,5 +21,38 @@ namespace Discord


public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IGuildUser user) => public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IGuildUser user) =>
builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl()); builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl());

public static EmbedBuilder ToEmbedBuilder(this IEmbed embed)
{
if (embed.Type != EmbedType.Rich)
throw new InvalidOperationException($"Only {nameof(EmbedType.Rich)} embeds may be built.");

var builder = new EmbedBuilder
{
Author = new EmbedAuthorBuilder
{
Name = embed.Author?.Name,
IconUrl = embed.Author?.IconUrl,
Url = embed.Author?.Url
},
Color = embed.Color ?? Color.Default,
Description = embed.Description,
Footer = new EmbedFooterBuilder
{
Text = embed.Footer?.Text,
IconUrl = embed.Footer?.IconUrl
},
ImageUrl = embed.Image?.Url,
ThumbnailUrl = embed.Thumbnail?.Url,
Timestamp = embed.Timestamp,
Title = embed.Title,
Url = embed.Url
};

foreach (var field in embed.Fields)
builder.AddField(field.Name, field.Value, field.Inline);

return builder;
}
} }
} }

+ 2
- 2
src/Discord.Net.Rest/Net/DefaultRestClient.cs View File

@@ -22,7 +22,7 @@ namespace Discord.Net.Rest
private CancellationToken _cancelToken; private CancellationToken _cancelToken;
private bool _isDisposed; private bool _isDisposed;


public DefaultRestClient(string baseUrl)
public DefaultRestClient(string baseUrl, bool useProxy = false)
{ {
_baseUrl = baseUrl; _baseUrl = baseUrl;


@@ -30,7 +30,7 @@ namespace Discord.Net.Rest
{ {
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
UseCookies = false, UseCookies = false,
UseProxy = false
UseProxy = useProxy,
}); });
SetHeader("accept-encoding", "gzip, deflate"); SetHeader("accept-encoding", "gzip, deflate");




+ 14
- 9
src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs View File

@@ -4,16 +4,21 @@ namespace Discord.Net.Rest
{ {
public static class DefaultRestClientProvider public static class DefaultRestClientProvider
{ {
public static readonly RestClientProvider Instance = url =>
public static readonly RestClientProvider Instance = Create();

public static RestClientProvider Create(bool useProxy = false)
{ {
try
{
return new DefaultRestClient(url);
}
catch (PlatformNotSupportedException ex)
return url =>
{ {
throw new PlatformNotSupportedException("The default RestClientProvider is not supported on this platform.", ex);
}
};
try
{
return new DefaultRestClient(url, useProxy);
}
catch (PlatformNotSupportedException ex)
{
throw new PlatformNotSupportedException("The default RestClientProvider is not supported on this platform.", ex);
}
};
}
} }
} }

+ 0
- 5
src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs View File

@@ -53,11 +53,6 @@ namespace Discord.Rpc
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);
public IDisposable EnterTypingState(RequestOptions options = null) public IDisposable EnterTypingState(RequestOptions options = null)


+ 0
- 5
src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs View File

@@ -56,11 +56,6 @@ namespace Discord.Rpc
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);
public IDisposable EnterTypingState(RequestOptions options = null) public IDisposable EnterTypingState(RequestOptions options = null)


+ 1
- 1
src/Discord.Net.Rpc/Entities/Channels/RpcGuildChannel.cs View File

@@ -52,7 +52,7 @@ namespace Discord.Rpc


public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
=> await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);


public override string ToString() => Name; public override string ToString() => Name;


+ 0
- 2
src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs View File

@@ -13,8 +13,6 @@ namespace Discord.API.Gateway
public IDictionary<string, string> Properties { get; set; } public IDictionary<string, string> Properties { get; set; }
[JsonProperty("large_threshold")] [JsonProperty("large_threshold")]
public int LargeThreshold { get; set; } public int LargeThreshold { get; set; }
[JsonProperty("compress")]
public bool UseCompression { get; set; }
[JsonProperty("shard")] [JsonProperty("shard")]
public Optional<int[]> ShardingParams { get; set; } public Optional<int[]> ShardingParams { get; set; }
} }


+ 193
- 0
src/Discord.Net.WebSocket/BaseSocketClient.Events.cs View File

@@ -0,0 +1,193 @@
using System;
using System.Threading.Tasks;

namespace Discord.WebSocket
{
public partial class BaseSocketClient
{
//Channels
/// <summary> Fired when a channel is created. </summary>
public event Func<SocketChannel, Task> ChannelCreated
{
add { _channelCreatedEvent.Add(value); }
remove { _channelCreatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<SocketChannel, Task>>();
/// <summary> Fired when a channel is destroyed. </summary>
public event Func<SocketChannel, Task> ChannelDestroyed {
add { _channelDestroyedEvent.Add(value); }
remove { _channelDestroyedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<SocketChannel, Task>>();
/// <summary> Fired when a channel is updated. </summary>
public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated {
add { _channelUpdatedEvent.Add(value); }
remove { _channelUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>();

//Messages
/// <summary> Fired when a message is received. </summary>
public event Func<SocketMessage, Task> MessageReceived {
add { _messageReceivedEvent.Add(value); }
remove { _messageReceivedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<SocketMessage, Task>>();
/// <summary> Fired when a message is deleted. </summary>
public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted {
add { _messageDeletedEvent.Add(value); }
remove { _messageDeletedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>();
/// <summary> Fired when a message is updated. </summary>
public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated {
add { _messageUpdatedEvent.Add(value); }
remove { _messageUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>>();
/// <summary> Fired when a reaction is added to a message. </summary>
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded {
add { _reactionAddedEvent.Add(value); }
remove { _reactionAddedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>();
/// <summary> Fired when a reaction is removed from a message. </summary>
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved {
add { _reactionRemovedEvent.Add(value); }
remove { _reactionRemovedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>();
/// <summary> Fired when all reactions to a message are cleared. </summary>
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared {
add { _reactionsClearedEvent.Add(value); }
remove { _reactionsClearedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>();

//Roles
/// <summary> Fired when a role is created. </summary>
public event Func<SocketRole, Task> RoleCreated {
add { _roleCreatedEvent.Add(value); }
remove { _roleCreatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>();
/// <summary> Fired when a role is deleted. </summary>
public event Func<SocketRole, Task> RoleDeleted {
add { _roleDeletedEvent.Add(value); }
remove { _roleDeletedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>();
/// <summary> Fired when a role is updated. </summary>
public event Func<SocketRole, SocketRole, Task> RoleUpdated {
add { _roleUpdatedEvent.Add(value); }
remove { _roleUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketRole, SocketRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<SocketRole, SocketRole, Task>>();

//Guilds
/// <summary> Fired when the connected account joins a guild. </summary>
public event Func<SocketGuild, Task> JoinedGuild {
add { _joinedGuildEvent.Add(value); }
remove { _joinedGuildEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when the connected account leaves a guild. </summary>
public event Func<SocketGuild, Task> LeftGuild {
add { _leftGuildEvent.Add(value); }
remove { _leftGuildEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when a guild becomes available. </summary>
public event Func<SocketGuild, Task> GuildAvailable {
add { _guildAvailableEvent.Add(value); }
remove { _guildAvailableEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when a guild becomes unavailable. </summary>
public event Func<SocketGuild, Task> GuildUnavailable {
add { _guildUnavailableEvent.Add(value); }
remove { _guildUnavailableEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when offline guild members are downloaded. </summary>
public event Func<SocketGuild, Task> GuildMembersDownloaded {
add { _guildMembersDownloadedEvent.Add(value); }
remove { _guildMembersDownloadedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when a guild is updated. </summary>
public event Func<SocketGuild, SocketGuild, Task> GuildUpdated {
add { _guildUpdatedEvent.Add(value); }
remove { _guildUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>();

//Users
/// <summary> Fired when a user joins a guild. </summary>
public event Func<SocketGuildUser, Task> UserJoined {
add { _userJoinedEvent.Add(value); }
remove { _userJoinedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
/// <summary> Fired when a user leaves a guild. </summary>
public event Func<SocketGuildUser, Task> UserLeft {
add { _userLeftEvent.Add(value); }
remove { _userLeftEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
/// <summary> Fired when a user is banned from a guild. </summary>
public event Func<SocketUser, SocketGuild, Task> UserBanned {
add { _userBannedEvent.Add(value); }
remove { _userBannedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
/// <summary> Fired when a user is unbanned from a guild. </summary>
public event Func<SocketUser, SocketGuild, Task> UserUnbanned {
add { _userUnbannedEvent.Add(value); }
remove { _userUnbannedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
/// <summary> Fired when a user is updated. </summary>
public event Func<SocketUser, SocketUser, Task> UserUpdated {
add { _userUpdatedEvent.Add(value); }
remove { _userUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>();
/// <summary> Fired when a guild member is updated, or a member presence is updated. </summary>
public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated {
add { _guildMemberUpdatedEvent.Add(value); }
remove { _guildMemberUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>();
/// <summary> Fired when a user joins, leaves, or moves voice channels. </summary>
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated {
add { _userVoiceStateUpdatedEvent.Add(value); }
remove { _userVoiceStateUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>>();
/// <summary> Fired when the connected account is updated. </summary>
public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated {
add { _selfUpdatedEvent.Add(value); }
remove { _selfUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>();
/// <summary> Fired when a user starts typing. </summary>
public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping {
add { _userIsTypingEvent.Add(value); }
remove { _userIsTypingEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>();
/// <summary> Fired when a user joins a group channel. </summary>
public event Func<SocketGroupUser, Task> RecipientAdded {
add { _recipientAddedEvent.Add(value); }
remove { _recipientAddedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
/// <summary> Fired when a user is removed from a group channel. </summary>
public event Func<SocketGroupUser, Task> RecipientRemoved {
add { _recipientRemovedEvent.Add(value); }
remove { _recipientRemovedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
}
}

+ 93
- 0
src/Discord.Net.WebSocket/BaseSocketClient.cs View File

@@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Discord.API;
using Discord.Rest;

namespace Discord.WebSocket
{
public abstract partial class BaseSocketClient : BaseDiscordClient, IDiscordClient
{
protected readonly DiscordSocketConfig _baseconfig;

/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary>
public abstract int Latency { get; protected set; }
public abstract UserStatus Status { get; protected set; }
public abstract Game? Game { get; protected set; }

internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;

public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; }
public abstract IReadOnlyCollection<SocketGuild> Guilds { get; }
public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; }
public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; }

internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client)
: base(config, client) => _baseconfig = config;
private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
=> new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent);

/// <inheritdoc />
public abstract Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null);
/// <inheritdoc />
public abstract SocketUser GetUser(ulong id);
/// <inheritdoc />
public abstract SocketUser GetUser(string username, string discriminator);
/// <inheritdoc />
public abstract SocketChannel GetChannel(ulong id);
/// <inheritdoc />
public abstract SocketGuild GetGuild(ulong id);
/// <inheritdoc />
public abstract RestVoiceRegion GetVoiceRegion(string id);
/// <inheritdoc />
public abstract Task StartAsync();
/// <inheritdoc />
public abstract Task StopAsync();
public abstract Task SetStatusAsync(UserStatus status);
public abstract Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming);
public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds);

/// <inheritdoc />
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null)
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default);
/// <inheritdoc />
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null)
=> ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default);
/// <inheritdoc />
public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null)
=> ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default);
// IDiscordClient
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
=> await GetApplicationInfoAsync(options).ConfigureAwait(false);

Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IChannel>(GetChannel(id));
Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels);

async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options)
=> await GetConnectionsAsync(options).ConfigureAwait(false);

async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options)
=> await GetInviteAsync(inviteId, options).ConfigureAwait(false);

Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuild>(GetGuild(id));
Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds);

async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options)
=> await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false);

Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(id));
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(username, discriminator));

Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
=> Task.FromResult<IVoiceRegion>(GetVoiceRegion(id));
Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions);
}
}

+ 22
- 183
src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs View File

@@ -3,197 +3,36 @@ using System.Threading.Tasks;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
//TODO: Add event docstrings
public partial class DiscordShardedClient public partial class DiscordShardedClient
{ {
//Channels
public event Func<SocketChannel, Task> ChannelCreated
//General
/// <summary> Fired when a shard is connected to the Discord gateway. </summary>
public event Func<DiscordSocketClient, Task> ShardConnected
{ {
add { _channelCreatedEvent.Add(value); }
remove { _channelCreatedEvent.Remove(value); }
add { _shardConnectedEvent.Add(value); }
remove { _shardConnectedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<SocketChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<SocketChannel, Task>>();
public event Func<SocketChannel, Task> ChannelDestroyed
private readonly AsyncEvent<Func<DiscordSocketClient, Task>> _shardConnectedEvent = new AsyncEvent<Func<DiscordSocketClient, Task>>();
/// <summary> Fired when a shard is disconnected from the Discord gateway. </summary>
public event Func<Exception, DiscordSocketClient, Task> ShardDisconnected
{ {
add { _channelDestroyedEvent.Add(value); }
remove { _channelDestroyedEvent.Remove(value); }
add { _shardDisconnectedEvent.Add(value); }
remove { _shardDisconnectedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<SocketChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<SocketChannel, Task>>();
public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated
private readonly AsyncEvent<Func<Exception, DiscordSocketClient, Task>> _shardDisconnectedEvent = new AsyncEvent<Func<Exception, DiscordSocketClient, Task>>();
/// <summary> Fired when a guild data for a shard has finished downloading. </summary>
public event Func<DiscordSocketClient, Task> ShardReady
{ {
add { _channelUpdatedEvent.Add(value); }
remove { _channelUpdatedEvent.Remove(value); }
add { _shardReadyEvent.Add(value); }
remove { _shardReadyEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>();

//Messages
public event Func<SocketMessage, Task> MessageReceived
{
add { _messageReceivedEvent.Add(value); }
remove { _messageReceivedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<SocketMessage, Task>>();
public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted
{
add { _messageDeletedEvent.Add(value); }
remove { _messageDeletedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>();
public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated
{
add { _messageUpdatedEvent.Add(value); }
remove { _messageUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>>();
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded
{
add { _reactionAddedEvent.Add(value); }
remove { _reactionAddedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>();
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved
{
add { _reactionRemovedEvent.Add(value); }
remove { _reactionRemovedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>();
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared
{
add { _reactionsClearedEvent.Add(value); }
remove { _reactionsClearedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>();

//Roles
public event Func<SocketRole, Task> RoleCreated
{
add { _roleCreatedEvent.Add(value); }
remove { _roleCreatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>();
public event Func<SocketRole, Task> RoleDeleted
{
add { _roleDeletedEvent.Add(value); }
remove { _roleDeletedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>();
public event Func<SocketRole, SocketRole, Task> RoleUpdated
{
add { _roleUpdatedEvent.Add(value); }
remove { _roleUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketRole, SocketRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<SocketRole, SocketRole, Task>>();

//Guilds
public event Func<SocketGuild, Task> JoinedGuild
{
add { _joinedGuildEvent.Add(value); }
remove { _joinedGuildEvent.Remove(value); }
}
private AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, Task> LeftGuild
{
add { _leftGuildEvent.Add(value); }
remove { _leftGuildEvent.Remove(value); }
}
private AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, Task> GuildAvailable
{
add { _guildAvailableEvent.Add(value); }
remove { _guildAvailableEvent.Remove(value); }
}
private AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, Task> GuildUnavailable
{
add { _guildUnavailableEvent.Add(value); }
remove { _guildUnavailableEvent.Remove(value); }
}
private AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, Task> GuildMembersDownloaded
{
add { _guildMembersDownloadedEvent.Add(value); }
remove { _guildMembersDownloadedEvent.Remove(value); }
}
private AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, SocketGuild, Task> GuildUpdated
{
add { _guildUpdatedEvent.Add(value); }
remove { _guildUpdatedEvent.Remove(value); }
}
private AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>();

//Users
public event Func<SocketGuildUser, Task> UserJoined
{
add { _userJoinedEvent.Add(value); }
remove { _userJoinedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
public event Func<SocketGuildUser, Task> UserLeft
{
add { _userLeftEvent.Add(value); }
remove { _userLeftEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
public event Func<SocketUser, SocketGuild, Task> UserBanned
{
add { _userBannedEvent.Add(value); }
remove { _userBannedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
public event Func<SocketUser, SocketGuild, Task> UserUnbanned
{
add { _userUnbannedEvent.Add(value); }
remove { _userUnbannedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
public event Func<SocketUser, SocketUser, Task> UserUpdated
{
add { _userUpdatedEvent.Add(value); }
remove { _userUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>();
public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated
{
add { _guildMemberUpdatedEvent.Add(value); }
remove { _guildMemberUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>();
public event Func<Optional<SocketGuild>, SocketUser, SocketPresence, SocketPresence, Task> UserPresenceUpdated
{
add { _userPresenceUpdatedEvent.Add(value); }
remove { _userPresenceUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Optional<SocketGuild>, SocketUser, SocketPresence, SocketPresence, Task>> _userPresenceUpdatedEvent = new AsyncEvent<Func<Optional<SocketGuild>, SocketUser, SocketPresence, SocketPresence, Task>>();
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated
{
add { _userVoiceStateUpdatedEvent.Add(value); }
remove { _userVoiceStateUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>>();
public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated
{
add { _selfUpdatedEvent.Add(value); }
remove { _selfUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>();
public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping
{
add { _userIsTypingEvent.Add(value); }
remove { _userIsTypingEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>();
public event Func<SocketGroupUser, Task> RecipientAdded
{
add { _recipientAddedEvent.Add(value); }
remove { _recipientAddedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
public event Func<SocketGroupUser, Task> RecipientRemoved
private readonly AsyncEvent<Func<DiscordSocketClient, Task>> _shardReadyEvent = new AsyncEvent<Func<DiscordSocketClient, Task>>();
/// <summary> Fired when a shard receives a heartbeat from the Discord gateway. </summary>
public event Func<int, int, DiscordSocketClient, Task> ShardLatencyUpdated
{ {
add { _recipientRemovedEvent.Add(value); }
remove { _recipientRemovedEvent.Remove(value); }
add { _shardLatencyUpdatedEvent.Add(value); }
remove { _shardLatencyUpdatedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
private readonly AsyncEvent<Func<int, int, DiscordSocketClient, Task>> _shardLatencyUpdatedEvent = new AsyncEvent<Func<int, int, DiscordSocketClient, Task>>();
} }
}
}

+ 29
- 39
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

@@ -9,7 +9,7 @@ using System.Threading;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
public partial class DiscordShardedClient : BaseDiscordClient, IDiscordClient
public partial class DiscordShardedClient : BaseSocketClient, IDiscordClient
{ {
private readonly DiscordSocketConfig _baseConfig; private readonly DiscordSocketConfig _baseConfig;
private readonly SemaphoreSlim _connectionGroupLock; private readonly SemaphoreSlim _connectionGroupLock;
@@ -20,16 +20,15 @@ namespace Discord.WebSocket
private bool _automaticShards; private bool _automaticShards;
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary>
public int Latency => GetLatency();
public UserStatus Status => _shards[0].Status;
public Game? Game => _shards[0].Game;
public override int Latency { get => GetLatency(); protected set { } }
public override UserStatus Status { get => _shards[0].Status; protected set { } }
public override Game? Game { get => _shards[0].Game; protected set { } }


internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } }
public IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount());
public IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount());
public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount());
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount());
public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; public IReadOnlyCollection<DiscordSocketClient> Shards => _shards;
public IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions;
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions;


/// <summary> Creates a new REST/WebSocket discord client. </summary> /// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { }
@@ -115,15 +114,11 @@ namespace Discord.WebSocket
} }


/// <inheritdoc /> /// <inheritdoc />
public async Task StartAsync()
{
await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false);
}
public override async Task StartAsync()
=> await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />
public async Task StopAsync()
{
await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false);
}
public override async Task StopAsync()
=> await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false);


public DiscordSocketClient GetShard(int id) public DiscordSocketClient GetShard(int id)
{ {
@@ -141,17 +136,15 @@ namespace Discord.WebSocket
=> GetShardFor(guild.Id); => GetShardFor(guild.Id);


/// <inheritdoc /> /// <inheritdoc />
public async Task<RestApplication> GetApplicationInfoAsync()
=> await _shards[0].GetApplicationInfoAsync().ConfigureAwait(false);
public override async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null)
=> await _shards[0].GetApplicationInfoAsync(options).ConfigureAwait(false);


/// <inheritdoc /> /// <inheritdoc />
public SocketGuild GetGuild(ulong id) => GetShardFor(id).GetGuild(id);
/// <inheritdoc />
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null)
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, new RequestOptions());
public override SocketGuild GetGuild(ulong id)
=> GetShardFor(id).GetGuild(id);


/// <inheritdoc /> /// <inheritdoc />
public SocketChannel GetChannel(ulong id)
public override SocketChannel GetChannel(ulong id)
{ {
for (int i = 0; i < _shards.Length; i++) for (int i = 0; i < _shards.Length; i++)
{ {
@@ -175,11 +168,7 @@ namespace Discord.WebSocket
for (int i = 0; i < _shards.Length; i++) for (int i = 0; i < _shards.Length; i++)
result += _shards[i].PrivateChannels.Count; result += _shards[i].PrivateChannels.Count;
return result; return result;
}

/// <inheritdoc />
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync()
=> ClientHelper.GetConnectionsAsync(this, new RequestOptions());
}


private IEnumerable<SocketGuild> GetGuilds() private IEnumerable<SocketGuild> GetGuilds()
{ {
@@ -195,14 +184,10 @@ namespace Discord.WebSocket
for (int i = 0; i < _shards.Length; i++) for (int i = 0; i < _shards.Length; i++)
result += _shards[i].Guilds.Count; result += _shards[i].Guilds.Count;
return result; return result;
}
}


/// <inheritdoc /> /// <inheritdoc />
public Task<RestInvite> GetInviteAsync(string inviteId)
=> ClientHelper.GetInviteAsync(this, inviteId, new RequestOptions());

/// <inheritdoc />
public SocketUser GetUser(ulong id)
public override SocketUser GetUser(ulong id)
{ {
for (int i = 0; i < _shards.Length; i++) for (int i = 0; i < _shards.Length; i++)
{ {
@@ -213,7 +198,7 @@ namespace Discord.WebSocket
return null; return null;
} }
/// <inheritdoc /> /// <inheritdoc />
public SocketUser GetUser(string username, string discriminator)
public override SocketUser GetUser(string username, string discriminator)
{ {
for (int i = 0; i < _shards.Length; i++) for (int i = 0; i < _shards.Length; i++)
{ {
@@ -225,11 +210,11 @@ namespace Discord.WebSocket
} }


/// <inheritdoc /> /// <inheritdoc />
public RestVoiceRegion GetVoiceRegion(string id)
public override RestVoiceRegion GetVoiceRegion(string id)
=> _shards[0].GetVoiceRegion(id); => _shards[0].GetVoiceRegion(id);


/// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary>
public async Task DownloadUsersAsync(IEnumerable<SocketGuild> guilds)
public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds)
{ {
for (int i = 0; i < _shards.Length; i++) for (int i = 0; i < _shards.Length; i++)
{ {
@@ -248,12 +233,12 @@ namespace Discord.WebSocket
return (int)Math.Round(total / (double)_shards.Length); return (int)Math.Round(total / (double)_shards.Length);
} }


public async Task SetStatusAsync(UserStatus status)
public override async Task SetStatusAsync(UserStatus status)
{ {
for (int i = 0; i < _shards.Length; i++) for (int i = 0; i < _shards.Length; i++)
await _shards[i].SetStatusAsync(status).ConfigureAwait(false); await _shards[i].SetStatusAsync(status).ConfigureAwait(false);
} }
public async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming)
public override async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming)
{ {
for (int i = 0; i < _shards.Length; i++) for (int i = 0; i < _shards.Length; i++)
await _shards[i].SetGameAsync(name, streamUrl, streamType).ConfigureAwait(false); await _shards[i].SetGameAsync(name, streamUrl, streamType).ConfigureAwait(false);
@@ -281,6 +266,11 @@ namespace Discord.WebSocket
}; };
} }


client.Connected += () => _shardConnectedEvent.InvokeAsync(client);
client.Disconnected += (exception) => _shardDisconnectedEvent.InvokeAsync(exception, client);
client.Ready += () => _shardReadyEvent.InvokeAsync(client);
client.LatencyUpdated += (oldLatency, newLatency) => _shardLatencyUpdatedEvent.InvokeAsync(oldLatency, newLatency, client);

client.ChannelCreated += (channel) => _channelCreatedEvent.InvokeAsync(channel); client.ChannelCreated += (channel) => _channelCreatedEvent.InvokeAsync(channel);
client.ChannelDestroyed += (channel) => _channelDestroyedEvent.InvokeAsync(channel); client.ChannelDestroyed += (channel) => _channelDestroyedEvent.InvokeAsync(channel);
client.ChannelUpdated += (oldChannel, newChannel) => _channelUpdatedEvent.InvokeAsync(oldChannel, newChannel); client.ChannelUpdated += (oldChannel, newChannel) => _channelUpdatedEvent.InvokeAsync(oldChannel, newChannel);


+ 35
- 8
src/Discord.Net.WebSocket/DiscordSocketApiClient.cs View File

@@ -29,7 +29,11 @@ namespace Discord.API
private CancellationTokenSource _connectCancelToken; private CancellationTokenSource _connectCancelToken;
private string _gatewayUrl; private string _gatewayUrl;
private bool _isExplicitUrl; private bool _isExplicitUrl;

//Store our decompression streams for zlib shared state
private MemoryStream _compressed;
private DeflateStream _decompressor;

internal IWebSocketClient WebSocketClient { get; } internal IWebSocketClient WebSocketClient { get; }


public ConnectionState ConnectionState { get; private set; } public ConnectionState ConnectionState { get; private set; }
@@ -43,14 +47,29 @@ namespace Discord.API
_isExplicitUrl = true; _isExplicitUrl = true;
WebSocketClient = webSocketProvider(); WebSocketClient = webSocketProvider();
//WebSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+) //WebSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+)

WebSocketClient.BinaryMessage += async (data, index, count) => WebSocketClient.BinaryMessage += async (data, index, count) =>
{ {
using (var compressed = new MemoryStream(data, index + 2, count - 2))
using (var decompressed = new MemoryStream()) using (var decompressed = new MemoryStream())
{ {
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
zlib.CopyTo(decompressed);
if (data[0] == 0x78)
{
//Strip the zlib header
_compressed.Write(data, index + 2, count - 2);
_compressed.SetLength(count - 2);
}
else
{
_compressed.Write(data, index, count);
_compressed.SetLength(count);
}

//Reset positions so we don't run out of memory
_compressed.Position = 0;
_decompressor.CopyTo(decompressed);
_compressed.Position = 0;
decompressed.Position = 0; decompressed.Position = 0;

using (var reader = new StreamReader(decompressed)) using (var reader = new StreamReader(decompressed))
using (var jsonReader = new JsonTextReader(reader)) using (var jsonReader = new JsonTextReader(reader))
{ {
@@ -76,6 +95,7 @@ namespace Discord.API
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
}; };
} }

internal override void Dispose(bool disposing) internal override void Dispose(bool disposing)
{ {
if (!_isDisposed) if (!_isDisposed)
@@ -84,6 +104,8 @@ namespace Discord.API
{ {
_connectCancelToken?.Dispose(); _connectCancelToken?.Dispose();
(WebSocketClient as IDisposable)?.Dispose(); (WebSocketClient as IDisposable)?.Dispose();
_decompressor?.Dispose();
_compressed?.Dispose();
} }
_isDisposed = true; _isDisposed = true;
} }
@@ -105,6 +127,12 @@ namespace Discord.API
if (WebSocketClient == null) if (WebSocketClient == null)
throw new NotSupportedException("This client is not configured with websocket support."); throw new NotSupportedException("This client is not configured with websocket support.");


//Re-create streams to reset the zlib state
_compressed?.Dispose();
_decompressor?.Dispose();
_compressed = new MemoryStream();
_decompressor = new DeflateStream(_compressed, CompressionMode.Decompress);

ConnectionState = ConnectionState.Connecting; ConnectionState = ConnectionState.Connecting;
try try
{ {
@@ -115,7 +143,7 @@ namespace Discord.API
if (!_isExplicitUrl) if (!_isExplicitUrl)
{ {
var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false);
_gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}";
_gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream";
} }
await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false);


@@ -191,7 +219,7 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
return await SendAsync<GetBotGatewayResponse>("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false); return await SendAsync<GetBotGatewayResponse>("GET", () => "gateway/bot", 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)
public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, RequestOptions options = null)
{ {
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
var props = new Dictionary<string, string> var props = new Dictionary<string, string>
@@ -202,8 +230,7 @@ namespace Discord.API
{ {
Token = AuthToken, Token = AuthToken,
Properties = props, Properties = props,
LargeThreshold = largeThreshold,
UseCompression = useCompression,
LargeThreshold = largeThreshold
}; };
if (totalShards > 1) if (totalShards > 1)
msg.ShardingParams = new int[] { shardID, totalShards }; msg.ShardingParams = new int[] { shardID, totalShards };


+ 5
- 186
src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs View File

@@ -2,218 +2,37 @@
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.WebSocket namespace Discord.WebSocket
{
//TODO: Add event docstrings
{
public partial class DiscordSocketClient public partial class DiscordSocketClient
{ {
//General //General
/// <summary> Fired when connected to the Discord gateway. </summary>
public event Func<Task> Connected public event Func<Task> Connected
{ {
add { _connectedEvent.Add(value); } add { _connectedEvent.Add(value); }
remove { _connectedEvent.Remove(value); } remove { _connectedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
/// <summary> Fired when disconnected to the Discord gateway. </summary>
public event Func<Exception, Task> Disconnected public event Func<Exception, Task> Disconnected
{ {
add { _disconnectedEvent.Add(value); } add { _disconnectedEvent.Add(value); }
remove { _disconnectedEvent.Remove(value); } remove { _disconnectedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
/// <summary> Fired when guild data has finished downloading. </summary>
public event Func<Task> Ready public event Func<Task> Ready
{ {
add { _readyEvent.Add(value); } add { _readyEvent.Add(value); }
remove { _readyEvent.Remove(value); } remove { _readyEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<Task>> _readyEvent = new AsyncEvent<Func<Task>>(); private readonly AsyncEvent<Func<Task>> _readyEvent = new AsyncEvent<Func<Task>>();
/// <summary> Fired when a heartbeat is received from the Discord gateway. </summary>
public event Func<int, int, Task> LatencyUpdated public event Func<int, int, Task> LatencyUpdated
{ {
add { _latencyUpdatedEvent.Add(value); } add { _latencyUpdatedEvent.Add(value); }
remove { _latencyUpdatedEvent.Remove(value); } remove { _latencyUpdatedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>();

//Channels
public event Func<SocketChannel, Task> ChannelCreated
{
add { _channelCreatedEvent.Add(value); }
remove { _channelCreatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<SocketChannel, Task>>();
public event Func<SocketChannel, Task> ChannelDestroyed
{
add { _channelDestroyedEvent.Add(value); }
remove { _channelDestroyedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<SocketChannel, Task>>();
public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated
{
add { _channelUpdatedEvent.Add(value); }
remove { _channelUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>();

//Messages
public event Func<SocketMessage, Task> MessageReceived
{
add { _messageReceivedEvent.Add(value); }
remove { _messageReceivedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<SocketMessage, Task>>();
public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted
{
add { _messageDeletedEvent.Add(value); }
remove { _messageDeletedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>();
public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated
{
add { _messageUpdatedEvent.Add(value); }
remove { _messageUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>>();
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded
{
add { _reactionAddedEvent.Add(value); }
remove { _reactionAddedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>();
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved
{
add { _reactionRemovedEvent.Add(value); }
remove { _reactionRemovedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>();
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared
{
add { _reactionsClearedEvent.Add(value); }
remove { _reactionsClearedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>();

//Roles
public event Func<SocketRole, Task> RoleCreated
{
add { _roleCreatedEvent.Add(value); }
remove { _roleCreatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>();
public event Func<SocketRole, Task> RoleDeleted
{
add { _roleDeletedEvent.Add(value); }
remove { _roleDeletedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>();
public event Func<SocketRole, SocketRole, Task> RoleUpdated
{
add { _roleUpdatedEvent.Add(value); }
remove { _roleUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketRole, SocketRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<SocketRole, SocketRole, Task>>();

//Guilds
public event Func<SocketGuild, Task> JoinedGuild
{
add { _joinedGuildEvent.Add(value); }
remove { _joinedGuildEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, Task> LeftGuild
{
add { _leftGuildEvent.Add(value); }
remove { _leftGuildEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, Task> GuildAvailable
{
add { _guildAvailableEvent.Add(value); }
remove { _guildAvailableEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, Task> GuildUnavailable
{
add { _guildUnavailableEvent.Add(value); }
remove { _guildUnavailableEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, Task> GuildMembersDownloaded
{
add { _guildMembersDownloadedEvent.Add(value); }
remove { _guildMembersDownloadedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>();
public event Func<SocketGuild, SocketGuild, Task> GuildUpdated
{
add { _guildUpdatedEvent.Add(value); }
remove { _guildUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>();

//Users
public event Func<SocketGuildUser, Task> UserJoined
{
add { _userJoinedEvent.Add(value); }
remove { _userJoinedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
public event Func<SocketGuildUser, Task> UserLeft
{
add { _userLeftEvent.Add(value); }
remove { _userLeftEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
public event Func<SocketUser, SocketGuild, Task> UserBanned
{
add { _userBannedEvent.Add(value); }
remove { _userBannedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
public event Func<SocketUser, SocketGuild, Task> UserUnbanned
{
add { _userUnbannedEvent.Add(value); }
remove { _userUnbannedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
public event Func<SocketUser, SocketUser, Task> UserUpdated
{
add { _userUpdatedEvent.Add(value); }
remove { _userUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>();
public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated
{
add { _guildMemberUpdatedEvent.Add(value); }
remove { _guildMemberUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>();
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated
{
add { _userVoiceStateUpdatedEvent.Add(value); }
remove { _userVoiceStateUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>>();
public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated
{
add { _selfUpdatedEvent.Add(value); }
remove { _selfUpdatedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>();
public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping
{
add { _userIsTypingEvent.Add(value); }
remove { _userIsTypingEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>();
public event Func<SocketGroupUser, Task> RecipientAdded
{
add { _recipientAddedEvent.Add(value); }
remove { _recipientAddedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
public event Func<SocketGroupUser, Task> RecipientRemoved
{
add { _recipientRemovedEvent.Add(value); }
remove { _recipientRemovedEvent.Remove(value); }
}
private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
} }
} }

+ 29
- 52
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -1,4 +1,5 @@
using Discord.API;
#pragma warning disable CS0618
using Discord.API;
using Discord.API.Gateway; using Discord.API.Gateway;
using Discord.Logging; using Discord.Logging;
using Discord.Net.Converters; using Discord.Net.Converters;
@@ -19,7 +20,7 @@ using GameModel = Discord.API.Game;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
public partial class DiscordSocketClient : BaseDiscordClient, IDiscordClient
public partial class DiscordSocketClient : BaseSocketClient, IDiscordClient
{ {
private readonly ConcurrentQueue<ulong> _largeGuilds; private readonly ConcurrentQueue<ulong> _largeGuilds;
private readonly JsonSerializer _serializer; private readonly JsonSerializer _serializer;
@@ -44,10 +45,10 @@ namespace Discord.WebSocket
public int ShardId { get; } public int ShardId { get; }
/// <summary> Gets the current connection state of this client. </summary> /// <summary> Gets the current connection state of this client. </summary>
public ConnectionState ConnectionState => _connection.State; public ConnectionState ConnectionState => _connection.State;
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary>
public int Latency { get; private set; }
internal UserStatus Status { get; private set; } = UserStatus.Online;
internal Game? Game { get; private set; }
/// <inheritdoc />
public override int Latency { get; protected set; }
public override UserStatus Status { get; protected set; } = UserStatus.Online;
public override Game? Game { get; protected set; }


//From DiscordSocketConfig //From DiscordSocketConfig
internal int TotalShards { get; private set; } internal int TotalShards { get; private set; }
@@ -60,14 +61,13 @@ namespace Discord.WebSocket
internal int? HandlerTimeout { get; private set; } internal int? HandlerTimeout { get; private set; }


internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; private set => base.CurrentUser = value; }
public IReadOnlyCollection<SocketGuild> Guilds => State.Guilds;
public IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels;
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds;
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels;
public IReadOnlyCollection<SocketDMChannel> DMChannels public IReadOnlyCollection<SocketDMChannel> DMChannels
=> State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray();
public IReadOnlyCollection<SocketGroupChannel> GroupChannels public IReadOnlyCollection<SocketGroupChannel> GroupChannels
=> State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray();
public IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection();
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection();


/// <summary> Creates a new REST/WebSocket discord client. </summary> /// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordSocketClient() : this(new DiscordSocketConfig()) { } public DiscordSocketClient() : this(new DiscordSocketConfig()) { }
@@ -155,9 +155,9 @@ namespace Discord.WebSocket
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>();
} }


public async Task StartAsync()
public override async Task StartAsync()
=> await _connection.StartAsync().ConfigureAwait(false); => await _connection.StartAsync().ConfigureAwait(false);
public async Task StopAsync()
public override async Task StopAsync()
=> await _connection.StopAsync().ConfigureAwait(false); => await _connection.StopAsync().ConfigureAwait(false);
private async Task OnConnectingAsync() private async Task OnConnectingAsync()
@@ -231,44 +231,23 @@ namespace Discord.WebSocket
} }


/// <inheritdoc /> /// <inheritdoc />
public async Task<RestApplication> GetApplicationInfoAsync()
{
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, new RequestOptions()));
}

/// <inheritdoc />
public SocketGuild GetGuild(ulong id)
{
return State.GetGuild(id);
}
/// <inheritdoc />
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null)
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, new RequestOptions());

/// <inheritdoc />
public SocketChannel GetChannel(ulong id)
{
return State.GetChannel(id);
}
public override async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null)
=> _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false));


/// <inheritdoc /> /// <inheritdoc />
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync()
=> ClientHelper.GetConnectionsAsync(this, new RequestOptions());
public override SocketGuild GetGuild(ulong id)
=> State.GetGuild(id);


/// <inheritdoc /> /// <inheritdoc />
public Task<RestInvite> GetInviteAsync(string inviteId)
=> ClientHelper.GetInviteAsync(this, inviteId, new RequestOptions());
public override SocketChannel GetChannel(ulong id)
=> State.GetChannel(id);
/// <inheritdoc /> /// <inheritdoc />
public SocketUser GetUser(ulong id)
{
return State.GetUser(id);
}
public override SocketUser GetUser(ulong id)
=> State.GetUser(id);
/// <inheritdoc /> /// <inheritdoc />
public SocketUser GetUser(string username, string discriminator)
{
return State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username);
}
public override SocketUser GetUser(string username, string discriminator)
=> State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username);
internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model)
{ {
return state.GetOrAddUser(model.Id, x => return state.GetOrAddUser(model.Id, x =>
@@ -288,13 +267,11 @@ namespace Discord.WebSocket
return user; return user;
}); });
} }
internal void RemoveUser(ulong id)
{
State.RemoveUser(id);
}
internal void RemoveUser(ulong id)
=> State.RemoveUser(id);


/// <inheritdoc /> /// <inheritdoc />
public RestVoiceRegion GetVoiceRegion(string id)
public override RestVoiceRegion GetVoiceRegion(string id)
{ {
if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region)) if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region))
return region; return region;
@@ -302,7 +279,7 @@ namespace Discord.WebSocket
} }


/// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary>
public async Task DownloadUsersAsync(IEnumerable<IGuild> guilds)
public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds)
{ {
if (ConnectionState == ConnectionState.Connected) if (ConnectionState == ConnectionState.Connected)
{ {
@@ -340,7 +317,7 @@ namespace Discord.WebSocket
} }
} }


public async Task SetStatusAsync(UserStatus status)
public override async Task SetStatusAsync(UserStatus status)
{ {
Status = status; Status = status;
if (status == UserStatus.AFK) if (status == UserStatus.AFK)
@@ -349,7 +326,7 @@ namespace Discord.WebSocket
_statusSince = null; _statusSince = null;
await SendStatusAsync().ConfigureAwait(false); await SendStatusAsync().ConfigureAwait(false);
} }
public async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming)
public override async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming)
{ {
if (name != null) if (name != null)
Game = new Game(name, streamUrl, streamType); Game = new Game(name, streamUrl, streamType);


+ 0
- 5
src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs View File

@@ -76,11 +76,6 @@ namespace Discord.WebSocket
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);
public IDisposable EnterTypingState(RequestOptions options = null) public IDisposable EnterTypingState(RequestOptions options = null)


+ 0
- 5
src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs View File

@@ -104,11 +104,6 @@ namespace Discord.WebSocket
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);
public IDisposable EnterTypingState(RequestOptions options = null) public IDisposable EnterTypingState(RequestOptions options = null)


+ 3
- 3
src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs View File

@@ -119,7 +119,7 @@ namespace Discord.WebSocket


public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
=> await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);


public new virtual SocketGuildUser GetUser(ulong id) => null; public new virtual SocketGuildUser GetUser(ulong id) => null;
@@ -160,8 +160,8 @@ namespace Discord.WebSocket


//IChannel //IChannel
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); //Overriden in Text/Voice
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(id)); //Overriden in Text/Voice
=> Task.FromResult<IUser>(GetUser(id)); //Overridden in Text/Voice
} }
} }

+ 12
- 1
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -1,4 +1,5 @@
using Discord.Audio;
#pragma warning disable CS0618
using Discord.Audio;
using Discord.Rest; using Discord.Rest;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -437,6 +438,16 @@ namespace Discord.WebSocket
_downloaderPromise.TrySetResultAsync(true); _downloaderPromise.TrySetResultAsync(true);
} }


//Emotes
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetEmoteAsync(this, Discord, id, options);
public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null)
=> GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options);
public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null)
=> GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options);
public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null)
=> GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options);

//Voice States //Voice States
internal async Task<SocketVoiceState> AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model) internal async Task<SocketVoiceState> AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model)
{ {


+ 1
- 1
src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs View File

@@ -43,7 +43,7 @@ namespace Discord.WebSocket
public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id);
public AudioInStream AudioStream => Guild.GetAudioStream(Id); public AudioInStream AudioStream => Guild.GetAudioStream(Id);


/// <summary> The position of the user within the role hirearchy. </summary>
/// <summary> The position of the user within the role hierarchy. </summary>
/// <remarks> The returned value equal to the position of the highest role the user has, /// <remarks> The returned value equal to the position of the highest role the user has,
/// or int.MaxValue if user is the server owner. </remarks> /// or int.MaxValue if user is the server owner. </remarks>
public int Hierarchy public int Hierarchy


+ 5
- 2
src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs View File

@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@@ -23,18 +24,20 @@ namespace Discord.Net.WebSockets
private readonly SemaphoreSlim _lock; private readonly SemaphoreSlim _lock;
private readonly Dictionary<string, string> _headers; private readonly Dictionary<string, string> _headers;
private ClientWebSocket _client; private ClientWebSocket _client;
private IWebProxy _proxy;
private Task _task; private Task _task;
private CancellationTokenSource _cancelTokenSource; private CancellationTokenSource _cancelTokenSource;
private CancellationToken _cancelToken, _parentToken; private CancellationToken _cancelToken, _parentToken;
private bool _isDisposed, _isDisconnecting; private bool _isDisposed, _isDisconnecting;


public DefaultWebSocketClient()
public DefaultWebSocketClient(IWebProxy proxy = null)
{ {
_lock = new SemaphoreSlim(1, 1); _lock = new SemaphoreSlim(1, 1);
_cancelTokenSource = new CancellationTokenSource(); _cancelTokenSource = new CancellationTokenSource();
_cancelToken = CancellationToken.None; _cancelToken = CancellationToken.None;
_parentToken = CancellationToken.None; _parentToken = CancellationToken.None;
_headers = new Dictionary<string, string>(); _headers = new Dictionary<string, string>();
_proxy = proxy;
} }
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
@@ -70,7 +73,7 @@ namespace Discord.Net.WebSockets
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;


_client = new ClientWebSocket(); _client = new ClientWebSocket();
_client.Options.Proxy = null;
_client.Options.Proxy = _proxy;
_client.Options.KeepAliveInterval = TimeSpan.Zero; _client.Options.KeepAliveInterval = TimeSpan.Zero;
foreach (var header in _headers) foreach (var header in _headers)
{ {


+ 15
- 9
src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs View File

@@ -1,21 +1,27 @@
using System; using System;
using System.Net;


namespace Discord.Net.WebSockets namespace Discord.Net.WebSockets
{ {
public static class DefaultWebSocketProvider public static class DefaultWebSocketProvider
{ {
#if DEFAULTWEBSOCKET #if DEFAULTWEBSOCKET
public static readonly WebSocketProvider Instance = () =>
public static readonly WebSocketProvider Instance = Create();

public static WebSocketProvider Create(IWebProxy proxy = null)
{ {
try
{
return new DefaultWebSocketClient();
}
catch (PlatformNotSupportedException ex)
return () =>
{ {
throw new PlatformNotSupportedException("The default WebSocketProvider is not supported on this platform.", ex);
}
};
try
{
return new DefaultWebSocketClient(proxy);
}
catch (PlatformNotSupportedException ex)
{
throw new PlatformNotSupportedException("The default WebSocketProvider is not supported on this platform.", ex);
}
};
}
#else #else
public static readonly WebSocketProvider Instance = () => public static readonly WebSocketProvider Instance = () =>
{ {


+ 19
- 19
src/Discord.Net/Discord.Net.nuspec View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Discord.Net</id> <id>Discord.Net</id>
<version>2.0.0-alpha$suffix$</version>
<version>2.0.0-beta$suffix$</version>
<title>Discord.Net</title> <title>Discord.Net</title>
<authors>Discord.Net Contributors</authors> <authors>Discord.Net Contributors</authors>
<owners>RogueException</owners> <owners>RogueException</owners>
@@ -13,28 +13,28 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<dependencies> <dependencies>
<group targetFramework="net45"> <group targetFramework="net45">
<dependency id="Discord.Net.Core" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Rpc" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Core" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Rpc" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.0-beta$suffix$" />
</group> </group>
<group targetFramework="netstandard1.1"> <group targetFramework="netstandard1.1">
<dependency id="Discord.Net.Core" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Rpc" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Core" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Rpc" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.0-beta$suffix$" />
</group> </group>
<group targetFramework="netstandard1.3"> <group targetFramework="netstandard1.3">
<dependency id="Discord.Net.Core" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Rpc" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.0-alpha$suffix$" />
<dependency id="Discord.Net.Core" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Rpc" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.0-beta$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.0-beta$suffix$" />
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>


+ 324
- 0
test/Discord.Net.Tests/Tests.ChannelPermissions.cs View File

@@ -0,0 +1,324 @@
using System;
using System.Threading.Tasks;
using Xunit;

namespace Discord
{
public partial class Tests
{
[Fact]
public void TestChannelPermission()
{
var perm = new ChannelPermissions();

// check initial values
Assert.Equal((ulong)0, perm.RawValue);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// permissions list empty by default
Assert.Empty(perm.ToList());

// test modify with no parameters
var copy = perm.Modify();
Assert.Equal((ulong)0, copy.RawValue);

// test the values that are returned by ChannelPermission.All
Assert.Equal((ulong)0, ChannelPermissions.None.RawValue);

// for text channels
ulong textChannel = (ulong)( ChannelPermission.CreateInstantInvite
| ChannelPermission.ManageChannels
| ChannelPermission.AddReactions
| ChannelPermission.ReadMessages
| ChannelPermission.SendMessages
| ChannelPermission.SendTTSMessages
| ChannelPermission.ManageMessages
| ChannelPermission.EmbedLinks
| ChannelPermission.AttachFiles
| ChannelPermission.ReadMessageHistory
| ChannelPermission.MentionEveryone
| ChannelPermission.UseExternalEmojis
| ChannelPermission.ManageRoles
| ChannelPermission.ManageWebhooks);

Assert.Equal(textChannel, ChannelPermissions.Text.RawValue);

// voice channels
ulong voiceChannel = (ulong)(
ChannelPermission.CreateInstantInvite
| ChannelPermission.ManageChannels
| ChannelPermission.Connect
| ChannelPermission.Speak
| ChannelPermission.MuteMembers
| ChannelPermission.DeafenMembers
| ChannelPermission.MoveMembers
| ChannelPermission.UseVAD
| ChannelPermission.ManageRoles);

Assert.Equal(voiceChannel, ChannelPermissions.Voice.RawValue);

// DM Channels
ulong dmChannel = (ulong)(
ChannelPermission.ReadMessages
| ChannelPermission.SendMessages
| ChannelPermission.EmbedLinks
| ChannelPermission.AttachFiles
| ChannelPermission.ReadMessageHistory
| ChannelPermission.UseExternalEmojis
| ChannelPermission.Connect
| ChannelPermission.Speak
| ChannelPermission.UseVAD
);
Assert.Equal(dmChannel, ChannelPermissions.DM.RawValue);

// group channel
ulong groupChannel = (ulong)(
ChannelPermission.SendMessages
| ChannelPermission.EmbedLinks
| ChannelPermission.AttachFiles
| ChannelPermission.SendTTSMessages
| ChannelPermission.Connect
| ChannelPermission.Speak
| ChannelPermission.UseVAD
);
Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue);
}

public void TestChannelPermissionModify()
{
// test channel permission modify

var perm = new ChannelPermissions();

// ensure that the permission is initially false
Assert.False(perm.CreateInstantInvite);

// ensure that when modified it works
perm = perm.Modify(createInstantInvite: true);
Assert.True(perm.CreateInstantInvite);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.CreateInstantInvite);

// set false again, move on to next permission
perm = perm.Modify(createInstantInvite: false);
Assert.False(perm.CreateInstantInvite);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.ManageChannel);

perm = perm.Modify(manageChannel: true);
Assert.True(perm.ManageChannel);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageChannels);

perm = perm.Modify(manageChannel: false);
Assert.False(perm.ManageChannel);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.AddReactions);

perm = perm.Modify(addReactions: true);
Assert.True(perm.AddReactions);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.AddReactions);

perm = perm.Modify(addReactions: false);
Assert.False(perm.AddReactions);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.ReadMessages);

perm = perm.Modify(readMessages: true);
Assert.True(perm.ReadMessages);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessages);

perm = perm.Modify(readMessages: false);
Assert.False(perm.ReadMessages);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.SendMessages);

perm = perm.Modify(sendMessages: true);
Assert.True(perm.SendMessages);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendMessages);

perm = perm.Modify(sendMessages: false);
Assert.False(perm.SendMessages);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.SendTTSMessages);

perm = perm.Modify(sendTTSMessages: true);
Assert.True(perm.SendTTSMessages);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendTTSMessages);

perm = perm.Modify(sendTTSMessages: false);
Assert.False(perm.SendTTSMessages);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.ManageMessages);

perm = perm.Modify(manageMessages: true);
Assert.True(perm.ManageMessages);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageMessages);

perm = perm.Modify(manageMessages: false);
Assert.False(perm.ManageMessages);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.EmbedLinks);

perm = perm.Modify(embedLinks: true);
Assert.True(perm.EmbedLinks);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.EmbedLinks);

perm = perm.Modify(embedLinks: false);
Assert.False(perm.EmbedLinks);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.AttachFiles);

perm = perm.Modify(attachFiles: true);
Assert.True(perm.AttachFiles);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.AttachFiles);

perm = perm.Modify(attachFiles: false);
Assert.False(perm.AttachFiles);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.ReadMessageHistory);

perm = perm.Modify(readMessageHistory: true);
Assert.True(perm.ReadMessageHistory);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessageHistory);

perm = perm.Modify(readMessageHistory: false);
Assert.False(perm.ReadMessageHistory);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.MentionEveryone);

perm = perm.Modify(mentionEveryone: true);
Assert.True(perm.MentionEveryone);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.MentionEveryone);

perm = perm.Modify(mentionEveryone: false);
Assert.False(perm.MentionEveryone);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.UseExternalEmojis);

perm = perm.Modify(useExternalEmojis: true);
Assert.True(perm.UseExternalEmojis);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseExternalEmojis);

perm = perm.Modify(useExternalEmojis: false);
Assert.False(perm.UseExternalEmojis);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.Connect);

perm = perm.Modify(connect: true);
Assert.True(perm.Connect);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.Connect);

perm = perm.Modify(connect: false);
Assert.False(perm.Connect);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);
// individual permission test
Assert.False(perm.Speak);

perm = perm.Modify(speak: true);
Assert.True(perm.Speak);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.Speak);

perm = perm.Modify(speak: false);
Assert.False(perm.Speak);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.MuteMembers);

perm = perm.Modify(muteMembers: true);
Assert.True(perm.MuteMembers);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.MuteMembers);

perm = perm.Modify(muteMembers: false);
Assert.False(perm.MuteMembers);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.DeafenMembers);

perm = perm.Modify(deafenMembers: true);
Assert.True(perm.DeafenMembers);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.DeafenMembers);

perm = perm.Modify(deafenMembers: false);
Assert.False(perm.DeafenMembers);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.MoveMembers);

perm = perm.Modify(moveMembers: true);
Assert.True(perm.MoveMembers);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.MoveMembers);

perm = perm.Modify(moveMembers: false);
Assert.False(perm.MoveMembers);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.UseVAD);

perm = perm.Modify(useVoiceActivation: true);
Assert.True(perm.UseVAD);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseVAD);

perm = perm.Modify(useVoiceActivation: false);
Assert.False(perm.UseVAD);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.ManageRoles);

perm = perm.Modify(manageRoles: true);
Assert.True(perm.ManageRoles);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageRoles);

perm = perm.Modify(manageRoles: false);
Assert.False(perm.ManageRoles);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);

// individual permission test
Assert.False(perm.ManageWebhooks);

perm = perm.Modify(manageWebhooks: true);
Assert.True(perm.ManageWebhooks);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageWebhooks);

perm = perm.Modify(manageWebhooks: false);
Assert.False(perm.ManageWebhooks);
Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);
}

[Fact]
public void TestChannelTypeResolution()
{
ITextChannel someChannel = null;
// null channels will throw exception
Assert.Throws<ArgumentException>(() => ChannelPermissions.All(someChannel));
}
}
}

+ 304
- 0
test/Discord.Net.Tests/Tests.GuildPermissions.cs View File

@@ -0,0 +1,304 @@
using System;
using System.Threading.Tasks;
using Xunit;

namespace Discord
{
public partial class Tests
{
[Fact]
public void TestGuildPermission()
{
// Test Guild Permission Constructors
var perm = new GuildPermissions();

// the default raw value is 0
Assert.Equal((ulong)0, perm.RawValue);
// also check that it is the same as none
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// permissions list is empty by default
Assert.Empty(perm.ToList());
Assert.NotNull(perm.ToList());

// Test modify with no parameters
var copy = perm.Modify();
// ensure that the raw values match
Assert.Equal((ulong)0, copy.RawValue);

// test GuildPermissions.All
ulong sumOfAllGuildPermissions = 0;
foreach(var v in Enum.GetValues(typeof(GuildPermission)))
{
sumOfAllGuildPermissions |= (ulong)v;
}

// assert that the raw values match
Assert.Equal(sumOfAllGuildPermissions, GuildPermissions.All.RawValue);
Assert.Equal((ulong)0, GuildPermissions.None.RawValue);

// assert that GuildPermissions.All contains the same number of permissions as the
// GuildPermissions enum
Assert.Equal(Enum.GetValues(typeof(GuildPermission)).Length, GuildPermissions.All.ToList().Count);

// assert that webhook has the same raw value
ulong webHookPermissions = (ulong)(
GuildPermission.SendMessages | GuildPermission.SendTTSMessages | GuildPermission.EmbedLinks |
GuildPermission.AttachFiles);
Assert.Equal(webHookPermissions, GuildPermissions.Webhook.RawValue);
}

[Fact]
public void TestGuildPermissionModify()
{
var perm = new GuildPermissions();

// tests each of the parameters of Modify one by one

// test modify with each of the parameters
// test initially false state
Assert.False(perm.CreateInstantInvite);

// ensure that when we modify it the parameter works
perm = perm.Modify(createInstantInvite: true);
Assert.True(perm.CreateInstantInvite);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.CreateInstantInvite);

// set it false again, then move on to the next permission
perm = perm.Modify(createInstantInvite: false);
Assert.False(perm.CreateInstantInvite);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(kickMembers: true);
Assert.True(perm.KickMembers);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.KickMembers);

perm = perm.Modify(kickMembers: false);
Assert.False(perm.KickMembers);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(banMembers: true);
Assert.True(perm.BanMembers);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.BanMembers);

perm = perm.Modify(banMembers: false);
Assert.False(perm.BanMembers);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(administrator: true);
Assert.True(perm.Administrator);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.Administrator);

perm = perm.Modify(administrator: false);
Assert.False(perm.Administrator);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(manageChannels: true);
Assert.True(perm.ManageChannels);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageChannels);

perm = perm.Modify(manageChannels: false);
Assert.False(perm.ManageChannels);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(manageGuild: true);
Assert.True(perm.ManageGuild);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageGuild);

perm = perm.Modify(manageGuild: false);
Assert.False(perm.ManageGuild);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);


// individual permission test
perm = perm.Modify(addReactions: true);
Assert.True(perm.AddReactions);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.AddReactions);

perm = perm.Modify(addReactions: false);
Assert.False(perm.AddReactions);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);


// individual permission test
perm = perm.Modify(viewAuditLog: true);
Assert.True(perm.ViewAuditLog);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ViewAuditLog);

perm = perm.Modify(viewAuditLog: false);
Assert.False(perm.ViewAuditLog);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);


// individual permission test
perm = perm.Modify(readMessages: true);
Assert.True(perm.ReadMessages);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessages);

perm = perm.Modify(readMessages: false);
Assert.False(perm.ReadMessages);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);


// individual permission test
perm = perm.Modify(sendMessages: true);
Assert.True(perm.SendMessages);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendMessages);

perm = perm.Modify(sendMessages: false);
Assert.False(perm.SendMessages);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(embedLinks: true);
Assert.True(perm.EmbedLinks);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.EmbedLinks);

perm = perm.Modify(embedLinks: false);
Assert.False(perm.EmbedLinks);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(attachFiles: true);
Assert.True(perm.AttachFiles);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.AttachFiles);

perm = perm.Modify(attachFiles: false);
Assert.False(perm.AttachFiles);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(readMessageHistory: true);
Assert.True(perm.ReadMessageHistory);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessageHistory);

perm = perm.Modify(readMessageHistory: false);
Assert.False(perm.ReadMessageHistory);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(mentionEveryone: true);
Assert.True(perm.MentionEveryone);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.MentionEveryone);

perm = perm.Modify(mentionEveryone: false);
Assert.False(perm.MentionEveryone);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(useExternalEmojis: true);
Assert.True(perm.UseExternalEmojis);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseExternalEmojis);

perm = perm.Modify(useExternalEmojis: false);
Assert.False(perm.UseExternalEmojis);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(connect: true);
Assert.True(perm.Connect);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.Connect);

perm = perm.Modify(connect: false);
Assert.False(perm.Connect);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(speak: true);
Assert.True(perm.Speak);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.Speak);

perm = perm.Modify(speak: false);
Assert.False(perm.Speak);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(muteMembers: true);
Assert.True(perm.MuteMembers);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.MuteMembers);

perm = perm.Modify(muteMembers: false);
Assert.False(perm.MuteMembers);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(deafenMembers: true);
Assert.True(perm.DeafenMembers);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.DeafenMembers);

perm = perm.Modify(deafenMembers: false);
Assert.False(perm.DeafenMembers);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(moveMembers: true);
Assert.True(perm.MoveMembers);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.MoveMembers);

perm = perm.Modify(moveMembers: false);
Assert.False(perm.MoveMembers);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(useVoiceActivation: true);
Assert.True(perm.UseVAD);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseVAD);

perm = perm.Modify(useVoiceActivation: false);
Assert.False(perm.UseVAD);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(changeNickname: true);
Assert.True(perm.ChangeNickname);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ChangeNickname);

perm = perm.Modify(changeNickname: false);
Assert.False(perm.ChangeNickname);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(manageNicknames: true);
Assert.True(perm.ManageNicknames);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageNicknames);

perm = perm.Modify(manageNicknames: false);
Assert.False(perm.ManageNicknames);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(manageRoles: true);
Assert.True(perm.ManageRoles);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageRoles);

perm = perm.Modify(manageRoles: false);
Assert.False(perm.ManageRoles);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(manageWebhooks: true);
Assert.True(perm.ManageWebhooks);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageWebhooks);

perm = perm.Modify(manageWebhooks: false);
Assert.False(perm.ManageWebhooks);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

// individual permission test
perm = perm.Modify(manageEmojis: true);
Assert.True(perm.ManageEmojis);
Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageEmojis);

perm = perm.Modify(manageEmojis: false);
Assert.False(perm.ManageEmojis);
Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);

}

}
}

Loading…
Cancel
Save