@@ -1,7 +1,7 @@ | |||
| |||
Microsoft Visual Studio Solution File, Format Version 12.00 | |||
# Visual Studio 14 | |||
VisualStudioVersion = 14.0.25420.1 | |||
# Visual Studio 15 | |||
VisualStudioVersion = 15.0.25914.0 | |||
MinimumVisualStudioVersion = 10.0.40219.1 | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}" | |||
ProjectSection(SolutionItems) = preProject | |||
@@ -9,57 +9,109 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution | |||
README.md = README.md | |||
EndProjectSection | |||
EndProject | |||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}" | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net", "src\Discord.Net\Discord.Net.csproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}" | |||
EndProject | |||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" | |||
EndProject | |||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.csproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" | |||
EndProject | |||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" | |||
EndProject | |||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.csproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" | |||
EndProject | |||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj", "{688FD1D8-7F01-4539-B2E9-F473C5D699C7}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
Debug|Any CPU = Debug|Any CPU | |||
Debug|x64 = Debug|x64 | |||
Debug|x86 = Debug|x86 | |||
Release|Any CPU = Release|Any CPU | |||
Release|x64 = Release|x64 | |||
Release|x86 = Release|x86 | |||
EndGlobalSection | |||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x64.Build.0 = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x86.Build.0 = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x64.ActiveCfg = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x64.Build.0 = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x86.ActiveCfg = Debug|Any CPU | |||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x86.Build.0 = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x64.ActiveCfg = Debug|x64 | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x64.Build.0 = Debug|x64 | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x86.ActiveCfg = Debug|x86 | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x86.Build.0 = Debug|x86 | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x64.ActiveCfg = Release|x64 | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x64.Build.0 = Release|x64 | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x86.ActiveCfg = Release|x86 | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x86.Build.0 = Release|x86 | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x64.Build.0 = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x86.Build.0 = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x64.ActiveCfg = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x64.Build.0 = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x86.ActiveCfg = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x86.Build.0 = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x64.Build.0 = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x86.Build.0 = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.ActiveCfg = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.Build.0 = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x64.ActiveCfg = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x64.Build.0 = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x86.ActiveCfg = Debug|Any CPU | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x86.Build.0 = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x64.Build.0 = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x86.Build.0 = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x64.ActiveCfg = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x64.Build.0 = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x86.ActiveCfg = Debug|Any CPU | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x86.Build.0 = Debug|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x64.ActiveCfg = Debug|x64 | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x64.Build.0 = Debug|x64 | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x86.ActiveCfg = Debug|x86 | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x86.Build.0 = Debug|x86 | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x64.ActiveCfg = Release|x64 | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x64.Build.0 = Release|x64 | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x86.ActiveCfg = Release|x86 | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x86.Build.0 = Release|x86 | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
EndGlobalSection | |||
GlobalSection(NestedProjects) = preSolution | |||
{BFC6DC28-0351-4573-926A-D4124244C04F} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||
{5688A353-121E-40A1-8BFA-B17B91FB48FB} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||
EndGlobalSection | |||
EndGlobal |
@@ -1,4 +1,4 @@ | |||
# Discord.Net v1.0.0-beta | |||
# Discord.Net v1.0.0-beta2 | |||
[](https://www.myget.org/feed/Packages/discord-net) | |||
[](https://www.myget.org/) | |||
[](https://discord.gg/0SBTUU1wZTYLhAAW) | |||
@@ -19,20 +19,15 @@ Bleeding edge builds are available using our MyGet feed (`https://www.myget.org/ | |||
## Compiling | |||
In order to compile Discord.Net, you require the following: | |||
### Using Visual Studio 2015 | |||
- [VS2015 Update 3](https://www.microsoft.com/net/core#windows) | |||
- [.Net Core 1.0 VS Plugin](https://www.microsoft.com/net/core#windows) | |||
### Using Visual Studio | |||
- [Visual Studio 2017 RC](https://www.microsoft.com/net/core#windowsvs2017) | |||
### Using CLI | |||
- [.Net Core 1.0 SDK](https://www.microsoft.com/net/core) | |||
The .NET Core and Docker (Preview) workload is required during Visual Studio installation. | |||
## Known Issues | |||
### WebSockets | |||
The current stable .Net Core websocket package does not support Linux, or pre-Win8. | |||
### Using Command Line | |||
- [.Net Core 1.1 SDK](https://www.microsoft.com/net/download/core) | |||
#### Linux | |||
Add the latest version of `System.Net.WebSockets.Client` from the .Net Core MyGet feed (`https://dotnet.myget.org/F/dotnet-core/api/v3/index.json`) to your project. | |||
## Known Issues | |||
#### Windows 7 and earlier | |||
There is currently no workaround, track the issue [here](https://github.com/dotnet/corefx/issues/9503). | |||
### WebSockets (Win7 and earlier) | |||
.Net Core 1.1 does not support WebSockets on Win7 and earlier. Track the issue [here](https://github.com/dotnet/corefx/issues/9503). |
@@ -1,8 +1,15 @@ | |||
@echo Off | |||
dotnet restore | |||
dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" | |||
dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" | |||
dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" | |||
dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" | |||
dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" | |||
dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" | |||
dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" | |||
REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" |
@@ -13,8 +13,8 @@ title: Samples | |||
#### Changing the bot's status | |||
[!code-sharp[Bot Status](samples/faq/status.cs)] | |||
[!code-csharp[Bot Status](samples/faq/status.cs)] | |||
#### Sending a message to a channel | |||
[!code-csharp[Message to Channel](samples/faq/send_message.cs)] | |||
[!code-csharp[Message to Channel](samples/faq/send_message.cs)] |
@@ -1,8 +1,5 @@ | |||
public async Task ModifyStatus() | |||
{ | |||
await (await _client.GetCurrentUserAsync()).ModifyStatusAsync(x => | |||
{ | |||
x.Status = UserStatus.Idle; | |||
x.Game = new Game("Type !help for help"); | |||
}); | |||
} | |||
await _client.SetStatus(UserStatus.Idle); | |||
await _client.SetGame("Type !help for help"); | |||
} |
@@ -1,3 +0,0 @@ | |||
{ | |||
"projects": [ "src", "test" ] | |||
} |
@@ -6,7 +6,7 @@ namespace Discord.Commands | |||
public class CommandAttribute : Attribute | |||
{ | |||
public string Text { get; } | |||
public RunMode RunMode { get; set; } = RunMode.Sync; | |||
public RunMode RunMode { get; set; } = RunMode.Default; | |||
public CommandAttribute() | |||
{ | |||
@@ -0,0 +1,75 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.Commands | |||
{ | |||
/// <summary> | |||
/// This attribute requires that the bot has a speicifed permission in the channel a command is invoked in. | |||
/// </summary> | |||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||
public class RequireBotPermissionAttribute : PreconditionAttribute | |||
{ | |||
public GuildPermission? GuildPermission { get; } | |||
public ChannelPermission? ChannelPermission { get; } | |||
/// <summary> | |||
/// Require that the bot account has a specified GuildPermission | |||
/// </summary> | |||
/// <remarks>This precondition will always fail if the command is being invoked in a private channel.</remarks> | |||
/// <param name="permission">The GuildPermission that the bot must have. Multiple permissions can be specified by ORing or ANDing the permissions together.</param> | |||
public RequireBotPermissionAttribute(GuildPermission permission) | |||
{ | |||
GuildPermission = permission; | |||
ChannelPermission = null; | |||
} | |||
/// <summary> | |||
/// Require that the bot account has a specified ChannelPermission. | |||
/// </summary> | |||
/// <param name="permission">The ChannelPermission that the bot must have. Multiple permissions can be specified by ORing or ANDing the permissions together.</param> | |||
/// <example> | |||
/// <code language="c#"> | |||
/// [Command("permission")] | |||
/// [RequireBotPermission(ChannelPermission.ManageMessages)] | |||
/// public async Task Purge() | |||
/// { | |||
/// } | |||
/// </code> | |||
/// </example> | |||
public RequireBotPermissionAttribute(ChannelPermission permission) | |||
{ | |||
ChannelPermission = permission; | |||
GuildPermission = null; | |||
} | |||
public override async Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) | |||
{ | |||
var guildUser = await context.Guild.GetCurrentUserAsync(); | |||
if (GuildPermission.HasValue) | |||
{ | |||
if (guildUser == null) | |||
return PreconditionResult.FromError("Command must be used in a guild channel"); | |||
if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) | |||
return PreconditionResult.FromError($"Command requires guild permission {GuildPermission.Value}"); | |||
} | |||
if (ChannelPermission.HasValue) | |||
{ | |||
var guildChannel = context.Channel as IGuildChannel; | |||
ChannelPermissions perms; | |||
if (guildChannel != null) | |||
perms = guildUser.GetPermissions(guildChannel); | |||
else | |||
perms = ChannelPermissions.All(guildChannel); | |||
if (!perms.Has(ChannelPermission.Value)) | |||
return PreconditionResult.FromError($"Command requires channel permission {ChannelPermission.Value}"); | |||
} | |||
return PreconditionResult.FromSuccess(); | |||
} | |||
} | |||
} |
@@ -11,11 +11,27 @@ namespace Discord.Commands | |||
Group = 0x04 | |||
} | |||
/// <summary> | |||
/// Require that the command be invoked in a specified context. | |||
/// </summary> | |||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||
public class RequireContextAttribute : PreconditionAttribute | |||
{ | |||
public ContextType Contexts { get; } | |||
/// <summary> | |||
/// Require that the command be invoked in a specified context. | |||
/// </summary> | |||
/// <param name="contexts">The type of context the command can be invoked in. Multiple contexts can be speicifed by ORing the contexts together.</param> | |||
/// <example> | |||
/// <code language="c#"> | |||
/// [Command("private_only")] | |||
/// [RequireContext(ContextType.DM | ContextType.Group)] | |||
/// public async Task PrivateOnly() | |||
/// { | |||
/// } | |||
/// </code> | |||
/// </example> | |||
public RequireContextAttribute(ContextType contexts) | |||
{ | |||
Contexts = contexts; | |||
@@ -0,0 +1,23 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Discord; | |||
namespace Discord.Commands | |||
{ | |||
/// <summary> | |||
/// Require that the command is invoked by the owner of the bot. | |||
/// </summary> | |||
/// <remarks>This precondition will only work if the bot is a bot account.</remarks> | |||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||
public class RequireOwnerAttribute : PreconditionAttribute | |||
{ | |||
public override async Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) | |||
{ | |||
var application = await context.Client.GetApplicationInfoAsync(); | |||
if (context.User.Id == application.Owner.Id) return PreconditionResult.FromSuccess(); | |||
return PreconditionResult.FromError("Command can only be run by the owner of the bot"); | |||
} | |||
} | |||
} |
@@ -3,18 +3,40 @@ using System.Threading.Tasks; | |||
namespace Discord.Commands | |||
{ | |||
/// <summary> | |||
/// This attribute requires that the user invoking the command has a specified permission. | |||
/// </summary> | |||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] | |||
public class RequirePermissionAttribute : PreconditionAttribute | |||
public class RequireUserPermissionAttribute : PreconditionAttribute | |||
{ | |||
public GuildPermission? GuildPermission { get; } | |||
public ChannelPermission? ChannelPermission { get; } | |||
public RequirePermissionAttribute(GuildPermission permission) | |||
/// <summary> | |||
/// Require that the user invoking the command has a specified GuildPermission | |||
/// </summary> | |||
/// <remarks>This precondition will always fail if the command is being invoked in a private channel.</remarks> | |||
/// <param name="permission">The GuildPermission that the user must have. Multiple permissions can be specified by ORing or ANDing the permissions together.</param> | |||
public RequireUserPermissionAttribute(GuildPermission permission) | |||
{ | |||
GuildPermission = permission; | |||
ChannelPermission = null; | |||
} | |||
public RequirePermissionAttribute(ChannelPermission permission) | |||
/// <summary> | |||
/// Require that the user invoking the command has a specified ChannelPermission. | |||
/// </summary> | |||
/// <param name="permission">The ChannelPermission that the user must have. Multiple permissions can be specified by ORing or ANDing the permissions together.</param> | |||
/// <example> | |||
/// <code language="c#"> | |||
/// [Command("permission")] | |||
/// [RequireUserPermission(ChannelPermission.ReadMessageHistory & ChannelPermission.ReadMessages)] | |||
/// public async Task HasPermission() | |||
/// { | |||
/// await ReplyAsync("You can read messages and the message history!"); | |||
/// } | |||
/// </code> | |||
/// </example> | |||
public RequireUserPermissionAttribute(ChannelPermission permission) | |||
{ | |||
ChannelPermission = permission; | |||
GuildPermission = null; |
@@ -0,0 +1,120 @@ | |||
using System; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using System.Collections.Generic; | |||
namespace Discord.Commands.Builders | |||
{ | |||
public class CommandBuilder | |||
{ | |||
private readonly List<PreconditionAttribute> _preconditions; | |||
private readonly List<ParameterBuilder> _parameters; | |||
private readonly List<string> _aliases; | |||
public ModuleBuilder Module { get; } | |||
internal Func<CommandContext, object[], IDependencyMap, Task> Callback { get; set; } | |||
public string Name { get; set; } | |||
public string Summary { get; set; } | |||
public string Remarks { get; set; } | |||
public RunMode RunMode { get; set; } | |||
public int Priority { get; set; } | |||
public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions; | |||
public IReadOnlyList<ParameterBuilder> Parameters => _parameters; | |||
public IReadOnlyList<string> Aliases => _aliases; | |||
//Automatic | |||
internal CommandBuilder(ModuleBuilder module) | |||
{ | |||
Module = module; | |||
_preconditions = new List<PreconditionAttribute>(); | |||
_parameters = new List<ParameterBuilder>(); | |||
_aliases = new List<string>(); | |||
} | |||
//User-defined | |||
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<CommandContext, object[], IDependencyMap, Task> callback) | |||
: this(module) | |||
{ | |||
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); | |||
Discord.Preconditions.NotNull(callback, nameof(callback)); | |||
Callback = callback; | |||
_aliases.Add(primaryAlias); | |||
} | |||
public CommandBuilder WithName(string name) | |||
{ | |||
Name = name; | |||
return this; | |||
} | |||
public CommandBuilder WithSummary(string summary) | |||
{ | |||
Summary = summary; | |||
return this; | |||
} | |||
public CommandBuilder WithRemarks(string remarks) | |||
{ | |||
Remarks = remarks; | |||
return this; | |||
} | |||
public CommandBuilder WithRunMode(RunMode runMode) | |||
{ | |||
RunMode = runMode; | |||
return this; | |||
} | |||
public CommandBuilder WithPriority(int priority) | |||
{ | |||
Priority = priority; | |||
return this; | |||
} | |||
public CommandBuilder AddAliases(params string[] aliases) | |||
{ | |||
_aliases.AddRange(aliases); | |||
return this; | |||
} | |||
public CommandBuilder AddPrecondition(PreconditionAttribute precondition) | |||
{ | |||
_preconditions.Add(precondition); | |||
return this; | |||
} | |||
public CommandBuilder AddParameter(string name, Type type, Action<ParameterBuilder> createFunc) | |||
{ | |||
var param = new ParameterBuilder(this, name, type); | |||
createFunc(param); | |||
_parameters.Add(param); | |||
return this; | |||
} | |||
internal CommandBuilder AddParameter(Action<ParameterBuilder> createFunc) | |||
{ | |||
var param = new ParameterBuilder(this); | |||
createFunc(param); | |||
_parameters.Add(param); | |||
return this; | |||
} | |||
internal CommandInfo Build(ModuleInfo info, CommandService service) | |||
{ | |||
//Default name to first alias | |||
if (Name == null) | |||
Name = _aliases[0]; | |||
if (_parameters.Count > 0) | |||
{ | |||
var lastParam = _parameters[_parameters.Count - 1]; | |||
var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); | |||
if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) | |||
throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag."); | |||
var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); | |||
if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) | |||
throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag."); | |||
} | |||
return new CommandInfo(this, info, service); | |||
} | |||
} | |||
} |
@@ -0,0 +1,109 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
namespace Discord.Commands.Builders | |||
{ | |||
public class ModuleBuilder | |||
{ | |||
private readonly List<CommandBuilder> _commands; | |||
private readonly List<ModuleBuilder> _submodules; | |||
private readonly List<PreconditionAttribute> _preconditions; | |||
private readonly List<string> _aliases; | |||
public CommandService Service { get; } | |||
public ModuleBuilder Parent { get; } | |||
public string Name { get; set; } | |||
public string Summary { get; set; } | |||
public string Remarks { get; set; } | |||
public IReadOnlyList<CommandBuilder> Commands => _commands; | |||
public IReadOnlyList<ModuleBuilder> Modules => _submodules; | |||
public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions; | |||
public IReadOnlyList<string> Aliases => _aliases; | |||
//Automatic | |||
internal ModuleBuilder(CommandService service, ModuleBuilder parent) | |||
{ | |||
Service = service; | |||
Parent = parent; | |||
_commands = new List<CommandBuilder>(); | |||
_submodules = new List<ModuleBuilder>(); | |||
_preconditions = new List<PreconditionAttribute>(); | |||
_aliases = new List<string>(); | |||
} | |||
//User-defined | |||
internal ModuleBuilder(CommandService service, ModuleBuilder parent, string primaryAlias) | |||
: this(service, parent) | |||
{ | |||
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); | |||
_aliases = new List<string> { primaryAlias }; | |||
} | |||
public ModuleBuilder WithName(string name) | |||
{ | |||
Name = name; | |||
return this; | |||
} | |||
public ModuleBuilder WithSummary(string summary) | |||
{ | |||
Summary = summary; | |||
return this; | |||
} | |||
public ModuleBuilder WithRemarks(string remarks) | |||
{ | |||
Remarks = remarks; | |||
return this; | |||
} | |||
public ModuleBuilder AddAlias(params string[] newAliases) | |||
{ | |||
_aliases.AddRange(newAliases); | |||
return this; | |||
} | |||
public ModuleBuilder AddPrecondition(PreconditionAttribute precondition) | |||
{ | |||
_preconditions.Add(precondition); | |||
return this; | |||
} | |||
public ModuleBuilder AddCommand(string primaryAlias, Func<CommandContext, object[], IDependencyMap, Task> callback, Action<CommandBuilder> createFunc) | |||
{ | |||
var builder = new CommandBuilder(this, primaryAlias, callback); | |||
createFunc(builder); | |||
_commands.Add(builder); | |||
return this; | |||
} | |||
internal ModuleBuilder AddCommand(Action<CommandBuilder> createFunc) | |||
{ | |||
var builder = new CommandBuilder(this); | |||
createFunc(builder); | |||
_commands.Add(builder); | |||
return this; | |||
} | |||
public ModuleBuilder AddModule(string primaryAlias, Action<ModuleBuilder> createFunc) | |||
{ | |||
var builder = new ModuleBuilder(Service, this, primaryAlias); | |||
createFunc(builder); | |||
_submodules.Add(builder); | |||
return this; | |||
} | |||
internal ModuleBuilder AddModule(Action<ModuleBuilder> createFunc) | |||
{ | |||
var builder = new ModuleBuilder(Service, this); | |||
createFunc(builder); | |||
_submodules.Add(builder); | |||
return this; | |||
} | |||
public ModuleInfo Build(CommandService service) | |||
{ | |||
//Default name to first alias | |||
if (Name == null) | |||
Name = _aliases[0]; | |||
return new ModuleInfo(this, service); | |||
} | |||
} | |||
} |
@@ -0,0 +1,232 @@ | |||
using System; | |||
using System.Linq; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using System.Threading.Tasks; | |||
using Discord.Commands.Builders; | |||
namespace Discord.Commands | |||
{ | |||
internal static class ModuleClassBuilder | |||
{ | |||
private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo(); | |||
public static IEnumerable<TypeInfo> Search(Assembly assembly) | |||
{ | |||
foreach (var type in assembly.ExportedTypes) | |||
{ | |||
var typeInfo = type.GetTypeInfo(); | |||
if (IsValidModuleDefinition(typeInfo) && | |||
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute))) | |||
{ | |||
yield return typeInfo; | |||
} | |||
} | |||
} | |||
public static Dictionary<Type, ModuleInfo> Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service); | |||
public static Dictionary<Type, ModuleInfo> Build(IEnumerable<TypeInfo> validTypes, CommandService service) | |||
{ | |||
if (!validTypes.Any()) | |||
throw new InvalidOperationException("Could not find any valid modules from the given selection"); | |||
var topLevelGroups = validTypes.Where(x => x.DeclaringType == null); | |||
var subGroups = validTypes.Intersect(topLevelGroups); | |||
var builtTypes = new List<TypeInfo>(); | |||
var result = new Dictionary<Type, ModuleInfo>(); | |||
foreach (var typeInfo in topLevelGroups) | |||
{ | |||
// TODO: This shouldn't be the case; may be safe to remove? | |||
if (result.ContainsKey(typeInfo.AsType())) | |||
continue; | |||
var module = new ModuleBuilder(service, null); | |||
BuildModule(module, typeInfo, service); | |||
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); | |||
result[typeInfo.AsType()] = module.Build(service); | |||
} | |||
return result; | |||
} | |||
private static void BuildSubTypes(ModuleBuilder builder, IEnumerable<TypeInfo> subTypes, List<TypeInfo> builtTypes, CommandService service) | |||
{ | |||
foreach (var typeInfo in subTypes) | |||
{ | |||
if (!IsValidModuleDefinition(typeInfo)) | |||
continue; | |||
if (builtTypes.Contains(typeInfo)) | |||
continue; | |||
builder.AddModule((module) => { | |||
BuildModule(module, typeInfo, service); | |||
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); | |||
}); | |||
builtTypes.Add(typeInfo); | |||
} | |||
} | |||
private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service) | |||
{ | |||
var attributes = typeInfo.GetCustomAttributes(); | |||
foreach (var attribute in attributes) | |||
{ | |||
// TODO: C#7 type switch | |||
if (attribute is NameAttribute) | |||
builder.Name = (attribute as NameAttribute).Text; | |||
else if (attribute is SummaryAttribute) | |||
builder.Summary = (attribute as SummaryAttribute).Text; | |||
else if (attribute is RemarksAttribute) | |||
builder.Remarks = (attribute as RemarksAttribute).Text; | |||
else if (attribute is AliasAttribute) | |||
builder.AddAlias((attribute as AliasAttribute).Aliases); | |||
else if (attribute is GroupAttribute) | |||
{ | |||
var groupAttr = attribute as GroupAttribute; | |||
builder.Name = builder.Name ?? groupAttr.Prefix; | |||
builder.AddAlias(groupAttr.Prefix); | |||
} | |||
else if (attribute is PreconditionAttribute) | |||
builder.AddPrecondition(attribute as PreconditionAttribute); | |||
} | |||
if (builder.Name == null) | |||
builder.Name = typeInfo.Name; | |||
var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); | |||
foreach (var method in validCommands) | |||
{ | |||
builder.AddCommand((command) => { | |||
BuildCommand(command, typeInfo, method, service); | |||
}); | |||
} | |||
} | |||
private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service) | |||
{ | |||
var attributes = method.GetCustomAttributes(); | |||
foreach (var attribute in attributes) | |||
{ | |||
// TODO: C#7 type switch | |||
if (attribute is CommandAttribute) | |||
{ | |||
var cmdAttr = attribute as CommandAttribute; | |||
builder.AddAliases(cmdAttr.Text); | |||
builder.RunMode = cmdAttr.RunMode; | |||
builder.Name = builder.Name ?? cmdAttr.Text; | |||
} | |||
else if (attribute is NameAttribute) | |||
builder.Name = (attribute as NameAttribute).Text; | |||
else if (attribute is PriorityAttribute) | |||
builder.Priority = (attribute as PriorityAttribute).Priority; | |||
else if (attribute is SummaryAttribute) | |||
builder.Summary = (attribute as SummaryAttribute).Text; | |||
else if (attribute is RemarksAttribute) | |||
builder.Remarks = (attribute as RemarksAttribute).Text; | |||
else if (attribute is AliasAttribute) | |||
builder.AddAliases((attribute as AliasAttribute).Aliases); | |||
else if (attribute is PreconditionAttribute) | |||
builder.AddPrecondition(attribute as PreconditionAttribute); | |||
} | |||
if (builder.Name == null) | |||
builder.Name = method.Name; | |||
var parameters = method.GetParameters(); | |||
int pos = 0, count = parameters.Length; | |||
foreach (var paramInfo in parameters) | |||
{ | |||
builder.AddParameter((parameter) => { | |||
BuildParameter(parameter, paramInfo, pos++, count, service); | |||
}); | |||
} | |||
var createInstance = ReflectionUtils.CreateBuilder<ModuleBase>(typeInfo, service); | |||
builder.Callback = (ctx, args, map) => { | |||
var instance = createInstance(map); | |||
instance.Context = ctx; | |||
try | |||
{ | |||
return method.Invoke(instance, args) as Task ?? Task.CompletedTask; | |||
} | |||
finally{ | |||
(instance as IDisposable)?.Dispose(); | |||
} | |||
}; | |||
} | |||
private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service) | |||
{ | |||
var attributes = paramInfo.GetCustomAttributes(); | |||
var paramType = paramInfo.ParameterType; | |||
builder.Name = paramInfo.Name; | |||
builder.IsOptional = paramInfo.IsOptional; | |||
builder.DefaultValue = paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; | |||
foreach (var attribute in attributes) | |||
{ | |||
// TODO: C#7 type switch | |||
if (attribute is SummaryAttribute) | |||
builder.Summary = (attribute as SummaryAttribute).Text; | |||
else if (attribute is ParamArrayAttribute) | |||
{ | |||
builder.IsMultiple = true; | |||
paramType = paramType.GetElementType(); | |||
} | |||
else if (attribute is RemainderAttribute) | |||
{ | |||
if (position != count-1) | |||
throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); | |||
builder.IsRemainder = true; | |||
} | |||
} | |||
var reader = service.GetTypeReader(paramType); | |||
if (reader == null) | |||
{ | |||
var paramTypeInfo = paramType.GetTypeInfo(); | |||
if (paramTypeInfo.IsEnum) | |||
{ | |||
reader = EnumTypeReader.GetReader(paramType); | |||
service.AddTypeReader(paramType, reader); | |||
} | |||
else | |||
{ | |||
throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?"); | |||
} | |||
} | |||
builder.ParameterType = paramType; | |||
builder.TypeReader = reader; | |||
} | |||
private static bool IsValidModuleDefinition(TypeInfo typeInfo) | |||
{ | |||
return _moduleTypeInfo.IsAssignableFrom(typeInfo) && | |||
!typeInfo.IsAbstract; | |||
} | |||
private static bool IsValidCommandDefinition(MethodInfo methodInfo) | |||
{ | |||
return methodInfo.IsDefined(typeof(CommandAttribute)) && | |||
methodInfo.ReturnType == typeof(Task) && | |||
!methodInfo.IsStatic && | |||
!methodInfo.IsGenericMethod; | |||
} | |||
} | |||
} |
@@ -0,0 +1,79 @@ | |||
using System; | |||
using System.Reflection; | |||
namespace Discord.Commands.Builders | |||
{ | |||
public class ParameterBuilder | |||
{ | |||
public CommandBuilder Command { get; } | |||
public string Name { get; internal set; } | |||
public Type ParameterType { get; internal set; } | |||
public TypeReader TypeReader { get; set; } | |||
public bool IsOptional { get; set; } | |||
public bool IsRemainder { get; set; } | |||
public bool IsMultiple { get; set; } | |||
public object DefaultValue { get; set; } | |||
public string Summary { get; set; } | |||
//Automatic | |||
internal ParameterBuilder(CommandBuilder command) | |||
{ | |||
Command = command; | |||
} | |||
//User-defined | |||
internal ParameterBuilder(CommandBuilder command, string name, Type type) | |||
: this(command) | |||
{ | |||
Preconditions.NotNull(name, nameof(name)); | |||
Name = name; | |||
SetType(type); | |||
} | |||
internal void SetType(Type type) | |||
{ | |||
TypeReader = Command.Module.Service.GetTypeReader(type); | |||
if (type.GetTypeInfo().IsValueType) | |||
DefaultValue = Activator.CreateInstance(type); | |||
else if (type.IsArray) | |||
type = ParameterType.GetElementType(); | |||
ParameterType = type; | |||
} | |||
public ParameterBuilder WithSummary(string summary) | |||
{ | |||
Summary = summary; | |||
return this; | |||
} | |||
public ParameterBuilder WithDefault(object defaultValue) | |||
{ | |||
DefaultValue = defaultValue; | |||
return this; | |||
} | |||
public ParameterBuilder WithIsOptional(bool isOptional) | |||
{ | |||
IsOptional = isOptional; | |||
return this; | |||
} | |||
public ParameterBuilder WithIsRemainder(bool isRemainder) | |||
{ | |||
IsRemainder = isRemainder; | |||
return this; | |||
} | |||
public ParameterBuilder WithIsMultiple(bool isMultiple) | |||
{ | |||
IsMultiple = isMultiple; | |||
return this; | |||
} | |||
internal ParameterInfo Build(CommandInfo info) | |||
{ | |||
if (TypeReader == null) | |||
throw new InvalidOperationException($"No default TypeReader found, one must be specified"); | |||
return new ParameterInfo(this, info, Command.Module.Service); | |||
} | |||
} | |||
} |
@@ -1,284 +0,0 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Threading.Tasks; | |||
namespace Discord.Commands | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class CommandInfo | |||
{ | |||
private static readonly MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); | |||
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); | |||
private readonly Func<CommandContext, object[], IDependencyMap, Task> _action; | |||
public MethodInfo Source { get; } | |||
public ModuleInfo Module { get; } | |||
public string Name { get; } | |||
public string Summary { get; } | |||
public string Remarks { get; } | |||
public string Text { get; } | |||
public int Priority { get; } | |||
public bool HasVarArgs { get; } | |||
public RunMode RunMode { get; } | |||
public IReadOnlyList<string> Aliases { get; } | |||
public IReadOnlyList<CommandParameter> Parameters { get; } | |||
public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | |||
internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix) | |||
{ | |||
try | |||
{ | |||
Source = source; | |||
Module = module; | |||
Name = source.Name; | |||
if (attribute.Text == null) | |||
Text = groupPrefix; | |||
RunMode = attribute.RunMode; | |||
if (groupPrefix != "") | |||
groupPrefix += " "; | |||
if (attribute.Text != null) | |||
Text = groupPrefix + attribute.Text; | |||
var aliasesBuilder = ImmutableArray.CreateBuilder<string>(); | |||
aliasesBuilder.Add(Text); | |||
var aliasesAttr = source.GetCustomAttribute<AliasAttribute>(); | |||
if (aliasesAttr != null) | |||
aliasesBuilder.AddRange(aliasesAttr.Aliases.Select(x => groupPrefix + x)); | |||
Aliases = aliasesBuilder.ToImmutable(); | |||
var nameAttr = source.GetCustomAttribute<NameAttribute>(); | |||
if (nameAttr != null) | |||
Name = nameAttr.Text; | |||
var summary = source.GetCustomAttribute<SummaryAttribute>(); | |||
if (summary != null) | |||
Summary = summary.Text; | |||
var remarksAttr = source.GetCustomAttribute<RemarksAttribute>(); | |||
if (remarksAttr != null) | |||
Remarks = remarksAttr.Text; | |||
var priorityAttr = source.GetCustomAttribute<PriorityAttribute>(); | |||
Priority = priorityAttr?.Priority ?? 0; | |||
Parameters = BuildParameters(source); | |||
HasVarArgs = Parameters.Count > 0 ? Parameters[Parameters.Count - 1].IsMultiple : false; | |||
Preconditions = BuildPreconditions(source); | |||
_action = BuildAction(source); | |||
} | |||
catch (Exception ex) | |||
{ | |||
throw new Exception($"Failed to build command {source.DeclaringType.FullName}.{source.Name}", ex); | |||
} | |||
} | |||
public async Task<PreconditionResult> CheckPreconditions(CommandContext context, IDependencyMap map = null) | |||
{ | |||
if (map == null) | |||
map = DependencyMap.Empty; | |||
foreach (PreconditionAttribute precondition in Module.Preconditions) | |||
{ | |||
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||
if (!result.IsSuccess) | |||
return result; | |||
} | |||
foreach (PreconditionAttribute precondition in Preconditions) | |||
{ | |||
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||
if (!result.IsSuccess) | |||
return result; | |||
} | |||
return PreconditionResult.FromSuccess(); | |||
} | |||
public async Task<ParseResult> Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) | |||
{ | |||
if (!searchResult.IsSuccess) | |||
return ParseResult.FromError(searchResult); | |||
if (preconditionResult != null && !preconditionResult.Value.IsSuccess) | |||
return ParseResult.FromError(preconditionResult.Value); | |||
string input = searchResult.Text; | |||
var matchingAliases = Aliases.Where(alias => input.StartsWith(alias)); | |||
string matchingAlias = ""; | |||
foreach (string alias in matchingAliases) | |||
{ | |||
if (alias.Length > matchingAlias.Length) | |||
matchingAlias = alias; | |||
} | |||
input = input.Substring(matchingAlias.Length); | |||
return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); | |||
} | |||
public Task<ExecuteResult> Execute(CommandContext context, ParseResult parseResult, IDependencyMap map) | |||
{ | |||
if (!parseResult.IsSuccess) | |||
return Task.FromResult(ExecuteResult.FromError(parseResult)); | |||
var argList = new object[parseResult.ArgValues.Count]; | |||
for (int i = 0; i < parseResult.ArgValues.Count; i++) | |||
{ | |||
if (!parseResult.ArgValues[i].IsSuccess) | |||
return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); | |||
argList[i] = parseResult.ArgValues[i].Values.First().Value; | |||
} | |||
var paramList = new object[parseResult.ParamValues.Count]; | |||
for (int i = 0; i < parseResult.ParamValues.Count; i++) | |||
{ | |||
if (!parseResult.ParamValues[i].IsSuccess) | |||
return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); | |||
paramList[i] = parseResult.ParamValues[i].Values.First().Value; | |||
} | |||
return Execute(context, argList, paramList, map); | |||
} | |||
public async Task<ExecuteResult> Execute(CommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IDependencyMap map) | |||
{ | |||
if (map == null) | |||
map = DependencyMap.Empty; | |||
try | |||
{ | |||
var args = GenerateArgs(argList, paramList); | |||
switch (RunMode) | |||
{ | |||
case RunMode.Sync: //Always sync | |||
await _action(context, args, map).ConfigureAwait(false); | |||
break; | |||
case RunMode.Mixed: //Sync until first await statement | |||
var t1 = _action(context, args, map); | |||
break; | |||
case RunMode.Async: //Always async | |||
var t2 = Task.Run(() => _action(context, args, map)); | |||
break; | |||
} | |||
return ExecuteResult.FromSuccess(); | |||
} | |||
catch (Exception ex) | |||
{ | |||
return ExecuteResult.FromError(ex); | |||
} | |||
} | |||
private IReadOnlyList<PreconditionAttribute> BuildPreconditions(MethodInfo methodInfo) | |||
{ | |||
return methodInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray(); | |||
} | |||
private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo) | |||
{ | |||
var parameters = methodInfo.GetParameters(); | |||
var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length); | |||
for (int i = 0; i < parameters.Length; i++) | |||
{ | |||
var parameter = parameters[i]; | |||
var type = parameter.ParameterType; | |||
//Detect 'params' | |||
bool isMultiple = parameter.GetCustomAttribute<ParamArrayAttribute>() != null; | |||
if (isMultiple) | |||
type = type.GetElementType(); | |||
var reader = Module.Service.GetTypeReader(type); | |||
var typeInfo = type.GetTypeInfo(); | |||
//Detect enums | |||
if (reader == null && typeInfo.IsEnum) | |||
{ | |||
reader = EnumTypeReader.GetReader(type); | |||
Module.Service.AddTypeReader(type, reader); | |||
} | |||
if (reader == null) | |||
throw new InvalidOperationException($"{type.FullName} is not supported as a command parameter, are you missing a TypeReader?"); | |||
bool isRemainder = parameter.GetCustomAttribute<RemainderAttribute>() != null; | |||
if (isRemainder && i != parameters.Length - 1) | |||
throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); | |||
string name = parameter.Name; | |||
string summary = parameter.GetCustomAttribute<SummaryAttribute>()?.Text; | |||
bool isOptional = parameter.IsOptional; | |||
object defaultValue = parameter.HasDefaultValue ? parameter.DefaultValue : null; | |||
paramBuilder.Add(new CommandParameter(parameters[i], name, summary, type, reader, isOptional, isRemainder, isMultiple, defaultValue)); | |||
} | |||
return paramBuilder.ToImmutable(); | |||
} | |||
private Func<CommandContext, object[], IDependencyMap, Task> BuildAction(MethodInfo methodInfo) | |||
{ | |||
if (methodInfo.ReturnType != typeof(Task)) | |||
throw new InvalidOperationException("Commands must return a non-generic Task."); | |||
return (context, args, map) => | |||
{ | |||
var instance = Module.CreateInstance(map); | |||
instance.Context = context; | |||
try | |||
{ | |||
return methodInfo.Invoke(instance, args) as Task ?? Task.CompletedTask; | |||
} | |||
finally | |||
{ | |||
(instance as IDisposable)?.Dispose(); | |||
} | |||
}; | |||
} | |||
private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList) | |||
{ | |||
int argCount = Parameters.Count; | |||
var array = new object[Parameters.Count]; | |||
if (HasVarArgs) | |||
argCount--; | |||
int i = 0; | |||
foreach (var arg in argList) | |||
{ | |||
if (i == argCount) | |||
throw new InvalidOperationException("Command was invoked with too many parameters"); | |||
array[i++] = arg; | |||
} | |||
if (i < argCount) | |||
throw new InvalidOperationException("Command was invoked with too few parameters"); | |||
if (HasVarArgs) | |||
{ | |||
var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].ElementType, t => | |||
{ | |||
var method = _convertParamsMethod.MakeGenericMethod(t); | |||
return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>)); | |||
}); | |||
array[i] = func(paramsList); | |||
} | |||
return array; | |||
} | |||
private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList) | |||
=> paramsList.Cast<T>().ToArray(); | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => $"{Module.Name}.{Name} ({Text})"; | |||
} | |||
} |
@@ -15,7 +15,7 @@ namespace Discord.Commands | |||
public static async Task<ParseResult> ParseArgs(CommandInfo command, CommandContext context, string input, int startPos) | |||
{ | |||
CommandParameter curParam = null; | |||
ParameterInfo curParam = null; | |||
StringBuilder argBuilder = new StringBuilder(input.Length); | |||
int endPos = input.Length; | |||
var curPart = ParserPart.None; | |||
@@ -65,7 +65,9 @@ namespace Discord.Commands | |||
return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments."); | |||
else | |||
{ | |||
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null; | |||
if (curParam == null) | |||
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null; | |||
if (curParam != null && curParam.IsRemainder) | |||
{ | |||
argBuilder.Append(c); | |||
@@ -116,11 +118,7 @@ namespace Discord.Commands | |||
{ | |||
paramList.Add(typeReaderResult); | |||
if (curPos == endPos) | |||
{ | |||
curParam = null; | |||
curPart = ParserPart.None; | |||
} | |||
curPart = ParserPart.None; | |||
} | |||
else | |||
{ | |||
@@ -7,24 +7,30 @@ using System.Reflection; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Discord.Commands.Builders; | |||
namespace Discord.Commands | |||
{ | |||
public class CommandService | |||
{ | |||
private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo(); | |||
private readonly SemaphoreSlim _moduleLock; | |||
private readonly ConcurrentDictionary<Type, ModuleInfo> _moduleDefs; | |||
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | |||
private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; | |||
private readonly ConcurrentBag<ModuleInfo> _moduleDefs; | |||
private readonly CommandMap _map; | |||
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x.Value); | |||
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Value.Commands); | |||
internal readonly bool _caseSensitive; | |||
internal readonly RunMode _defaultRunMode; | |||
public CommandService() | |||
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); | |||
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); | |||
public CommandService() : this(new CommandServiceConfig()) { } | |||
public CommandService(CommandServiceConfig config) | |||
{ | |||
_moduleLock = new SemaphoreSlim(1, 1); | |||
_moduleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | |||
_typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | |||
_moduleDefs = new ConcurrentBag<ModuleInfo>(); | |||
_map = new CommandMap(); | |||
_typeReaders = new ConcurrentDictionary<Type, TypeReader> | |||
{ | |||
@@ -62,103 +68,129 @@ namespace Discord.Commands | |||
[typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(), | |||
[typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(), | |||
}; | |||
_caseSensitive = config.CaseSensitiveCommands; | |||
_defaultRunMode = config.DefaultRunMode; | |||
} | |||
//Modules | |||
public async Task<ModuleInfo> AddModule<T>() | |||
public async Task<ModuleInfo> CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc) | |||
{ | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
var typeInfo = typeof(T).GetTypeInfo(); | |||
if (!_moduleTypeInfo.IsAssignableFrom(typeInfo)) | |||
throw new ArgumentException($"Modules must inherit ModuleBase."); | |||
var builder = new ModuleBuilder(this, null, primaryAlias); | |||
buildFunc(builder); | |||
if (typeInfo.IsAbstract) | |||
throw new InvalidOperationException("Modules must not be abstract."); | |||
var module = builder.Build(this); | |||
return LoadModuleInternal(module); | |||
} | |||
finally | |||
{ | |||
_moduleLock.Release(); | |||
} | |||
} | |||
public async Task<ModuleInfo> AddModuleAsync<T>() | |||
{ | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
var typeInfo = typeof(T).GetTypeInfo(); | |||
if (_moduleDefs.ContainsKey(typeof(T))) | |||
if (_typedModuleDefs.ContainsKey(typeof(T))) | |||
throw new ArgumentException($"This module has already been added."); | |||
return AddModuleInternal(typeInfo); | |||
var module = ModuleClassBuilder.Build(this, typeInfo).FirstOrDefault(); | |||
if (module.Value == default(ModuleInfo)) | |||
throw new InvalidOperationException($"Could not build the module {typeof(T).FullName}, did you pass an invalid type?"); | |||
_typedModuleDefs[module.Key] = module.Value; | |||
return LoadModuleInternal(module.Value); | |||
} | |||
finally | |||
{ | |||
_moduleLock.Release(); | |||
} | |||
} | |||
public async Task<IEnumerable<ModuleInfo>> AddModules(Assembly assembly) | |||
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly) | |||
{ | |||
var moduleDefs = ImmutableArray.CreateBuilder<ModuleInfo>(); | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
foreach (var type in assembly.ExportedTypes) | |||
var types = ModuleClassBuilder.Search(assembly).ToArray(); | |||
var moduleDefs = ModuleClassBuilder.Build(types, this); | |||
foreach (var info in moduleDefs) | |||
{ | |||
if (!_moduleDefs.ContainsKey(type)) | |||
{ | |||
var typeInfo = type.GetTypeInfo(); | |||
if (_moduleTypeInfo.IsAssignableFrom(typeInfo)) | |||
{ | |||
var dontAutoLoad = typeInfo.GetCustomAttribute<DontAutoLoadAttribute>(); | |||
if (dontAutoLoad == null && !typeInfo.IsAbstract) | |||
moduleDefs.Add(AddModuleInternal(typeInfo)); | |||
} | |||
} | |||
_typedModuleDefs[info.Key] = info.Value; | |||
LoadModuleInternal(info.Value); | |||
} | |||
return moduleDefs.ToImmutable(); | |||
return moduleDefs.Select(x => x.Value).ToImmutableArray(); | |||
} | |||
finally | |||
{ | |||
_moduleLock.Release(); | |||
} | |||
} | |||
private ModuleInfo AddModuleInternal(TypeInfo typeInfo) | |||
private ModuleInfo LoadModuleInternal(ModuleInfo module) | |||
{ | |||
var moduleDef = new ModuleInfo(typeInfo, this); | |||
_moduleDefs[typeInfo.AsType()] = moduleDef; | |||
_moduleDefs.Add(module); | |||
foreach (var cmd in moduleDef.Commands) | |||
_map.AddCommand(cmd); | |||
foreach (var command in module.Commands) | |||
_map.AddCommand(command); | |||
return moduleDef; | |||
foreach (var submodule in module.Submodules) | |||
LoadModuleInternal(submodule); | |||
return module; | |||
} | |||
public async Task<bool> RemoveModule(ModuleInfo module) | |||
public async Task<bool> RemoveModuleAsync(ModuleInfo module) | |||
{ | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
return RemoveModuleInternal(module.Source.BaseType); | |||
return RemoveModuleInternal(module); | |||
} | |||
finally | |||
{ | |||
_moduleLock.Release(); | |||
} | |||
} | |||
public async Task<bool> RemoveModule<T>() | |||
public async Task<bool> RemoveModuleAsync<T>() | |||
{ | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
return RemoveModuleInternal(typeof(T)); | |||
ModuleInfo module; | |||
_typedModuleDefs.TryGetValue(typeof(T), out module); | |||
if (module == default(ModuleInfo)) | |||
return false; | |||
return RemoveModuleInternal(module); | |||
} | |||
finally | |||
{ | |||
_moduleLock.Release(); | |||
} | |||
} | |||
private bool RemoveModuleInternal(Type type) | |||
private bool RemoveModuleInternal(ModuleInfo module) | |||
{ | |||
ModuleInfo unloadedModule; | |||
if (_moduleDefs.TryRemove(type, out unloadedModule)) | |||
var defsRemove = module; | |||
if (!_moduleDefs.TryTake(out defsRemove)) | |||
return false; | |||
foreach (var cmd in module.Commands) | |||
_map.RemoveCommand(cmd); | |||
foreach (var submodule in module.Submodules) | |||
{ | |||
foreach (var cmd in unloadedModule.Commands) | |||
_map.RemoveCommand(cmd); | |||
return true; | |||
RemoveModuleInternal(submodule); | |||
} | |||
else | |||
return false; | |||
return true; | |||
} | |||
//Type Readers | |||
@@ -182,7 +214,7 @@ namespace Discord.Commands | |||
public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); | |||
public SearchResult Search(CommandContext context, string input) | |||
{ | |||
string lowerInput = input.ToLowerInvariant(); | |||
input = _caseSensitive ? input : input.ToLowerInvariant(); | |||
var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray(); | |||
if (matches.Length > 0) | |||
@@ -191,9 +223,9 @@ namespace Discord.Commands | |||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | |||
} | |||
public Task<IResult> Execute(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
=> Execute(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); | |||
public async Task<IResult> Execute(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
public Task<IResult> ExecuteAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); | |||
public async Task<IResult> ExecuteAsync(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
{ | |||
dependencyMap = dependencyMap ?? DependencyMap.Empty; | |||
@@ -204,7 +236,7 @@ namespace Discord.Commands | |||
var commands = searchResult.Commands; | |||
for (int i = commands.Count - 1; i >= 0; i--) | |||
{ | |||
var preconditionResult = await commands[i].CheckPreconditions(context, dependencyMap).ConfigureAwait(false); | |||
var preconditionResult = await commands[i].CheckPreconditionsAsync(context, dependencyMap).ConfigureAwait(false); | |||
if (!preconditionResult.IsSuccess) | |||
{ | |||
if (commands.Count == 1) | |||
@@ -213,7 +245,7 @@ namespace Discord.Commands | |||
continue; | |||
} | |||
var parseResult = await commands[i].Parse(context, searchResult, preconditionResult).ConfigureAwait(false); | |||
var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false); | |||
if (!parseResult.IsSuccess) | |||
{ | |||
if (parseResult.Error == CommandError.MultipleMatches) | |||
@@ -0,0 +1,10 @@ | |||
namespace Discord.Commands | |||
{ | |||
public class CommandServiceConfig | |||
{ | |||
/// <summary> The default RunMode commands should have, if one is not specified on the Command attribute or builder. </summary> | |||
public RunMode DefaultRunMode { get; set; } = RunMode.Mixed; | |||
/// <summary> Should commands be case-sensitive? </summary> | |||
public bool CaseSensitiveCommands { get; set; } = false; | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" /> | |||
<PropertyGroup> | |||
<Description>A Discord.Net extension adding support for bot commands.</Description> | |||
<VersionPrefix>1.0.0-beta2</VersionPrefix> | |||
<TargetFramework>netstandard1.3</TargetFramework> | |||
<AssemblyName>Discord.Net.Commands</AssemblyName> | |||
<PackageTags>discord;discordapp</PackageTags> | |||
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | |||
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | |||
<RepositoryType>git</RepositoryType> | |||
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl> | |||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Compile Include="**\*.cs" /> | |||
<EmbeddedResource Include="**\*.resx" /> | |||
<EmbeddedResource Include="compiler\resources\**\*" /> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.NET.Sdk"> | |||
<Version>1.0.0-alpha-20161104-2</Version> | |||
<PrivateAssets>All</PrivateAssets> | |||
</PackageReference> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<PropertyGroup Label="Configuration"> | |||
<SignAssembly>False</SignAssembly> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
</Project> |
@@ -1,19 +0,0 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||
<PropertyGroup Label="Globals"> | |||
<ProjectGuid>078dd7e6-943d-4d09-afc2-d2ba58b76c9c</ProjectGuid> | |||
<RootNamespace>Discord.Commands</RootNamespace> | |||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<SchemaVersion>2.0</SchemaVersion> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||
</Project> |
@@ -0,0 +1,22 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Discord.Commands | |||
{ | |||
public static class IEnumerableExtensions | |||
{ | |||
public static IEnumerable<TResult> Permutate<TFirst, TSecond, TResult>( | |||
this IEnumerable<TFirst> set, | |||
IEnumerable<TSecond> others, | |||
Func<TFirst, TSecond, TResult> func) | |||
{ | |||
foreach (TFirst elem in set) | |||
{ | |||
foreach (TSecond elem2 in others) | |||
{ | |||
yield return func(elem, elem2); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,194 @@ | |||
using System; | |||
using System.Linq; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Collections.Concurrent; | |||
using System.Threading.Tasks; | |||
using System.Reflection; | |||
using Discord.Commands.Builders; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
{ | |||
[DebuggerDisplay("{Name,nq}")] | |||
public class CommandInfo | |||
{ | |||
private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); | |||
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); | |||
private readonly Func<CommandContext, object[], IDependencyMap, Task> _action; | |||
public ModuleInfo Module { get; } | |||
public string Name { get; } | |||
public string Summary { get; } | |||
public string Remarks { get; } | |||
public int Priority { get; } | |||
public bool HasVarArgs { get; } | |||
public RunMode RunMode { get; } | |||
public IReadOnlyList<string> Aliases { get; } | |||
public IReadOnlyList<ParameterInfo> Parameters { get; } | |||
public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | |||
internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) | |||
{ | |||
Module = module; | |||
Name = builder.Name; | |||
Summary = builder.Summary; | |||
Remarks = builder.Remarks; | |||
RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode); | |||
Priority = builder.Priority; | |||
// both command and module provide aliases | |||
if (module.Aliases.Count > 0 && builder.Aliases.Count > 0) | |||
Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => second != null ? first + " " + second : first).Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray(); | |||
// only module provides aliases | |||
else if (module.Aliases.Count > 0) | |||
Aliases = module.Aliases.Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray(); | |||
// only command provides aliases | |||
else if (builder.Aliases.Count > 0) | |||
Aliases = builder.Aliases.Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray(); | |||
// neither provide aliases | |||
else | |||
throw new InvalidOperationException("Cannot build a command without any aliases"); | |||
Preconditions = builder.Preconditions.ToImmutableArray(); | |||
Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | |||
HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; | |||
_action = builder.Callback; | |||
} | |||
public async Task<PreconditionResult> CheckPreconditionsAsync(CommandContext context, IDependencyMap map = null) | |||
{ | |||
if (map == null) | |||
map = DependencyMap.Empty; | |||
foreach (PreconditionAttribute precondition in Module.Preconditions) | |||
{ | |||
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||
if (!result.IsSuccess) | |||
return result; | |||
} | |||
foreach (PreconditionAttribute precondition in Preconditions) | |||
{ | |||
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||
if (!result.IsSuccess) | |||
return result; | |||
} | |||
return PreconditionResult.FromSuccess(); | |||
} | |||
public async Task<ParseResult> ParseAsync(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) | |||
{ | |||
if (!searchResult.IsSuccess) | |||
return ParseResult.FromError(searchResult); | |||
if (preconditionResult != null && !preconditionResult.Value.IsSuccess) | |||
return ParseResult.FromError(preconditionResult.Value); | |||
string input = searchResult.Text; | |||
var matchingAliases = Aliases.Where(alias => input.StartsWith(alias)); | |||
string matchingAlias = ""; | |||
foreach (string alias in matchingAliases) | |||
{ | |||
if (alias.Length > matchingAlias.Length) | |||
matchingAlias = alias; | |||
} | |||
input = input.Substring(matchingAlias.Length); | |||
return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); | |||
} | |||
public Task<ExecuteResult> Execute(CommandContext context, ParseResult parseResult, IDependencyMap map) | |||
{ | |||
if (!parseResult.IsSuccess) | |||
return Task.FromResult(ExecuteResult.FromError(parseResult)); | |||
var argList = new object[parseResult.ArgValues.Count]; | |||
for (int i = 0; i < parseResult.ArgValues.Count; i++) | |||
{ | |||
if (!parseResult.ArgValues[i].IsSuccess) | |||
return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); | |||
argList[i] = parseResult.ArgValues[i].Values.First().Value; | |||
} | |||
var paramList = new object[parseResult.ParamValues.Count]; | |||
for (int i = 0; i < parseResult.ParamValues.Count; i++) | |||
{ | |||
if (!parseResult.ParamValues[i].IsSuccess) | |||
return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); | |||
paramList[i] = parseResult.ParamValues[i].Values.First().Value; | |||
} | |||
return ExecuteAsync(context, argList, paramList, map); | |||
} | |||
public async Task<ExecuteResult> ExecuteAsync(CommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IDependencyMap map) | |||
{ | |||
if (map == null) | |||
map = DependencyMap.Empty; | |||
try | |||
{ | |||
var args = GenerateArgs(argList, paramList); | |||
switch (RunMode) | |||
{ | |||
case RunMode.Sync: //Always sync | |||
await _action(context, args, map).ConfigureAwait(false); | |||
break; | |||
case RunMode.Mixed: //Sync until first await statement | |||
var t1 = _action(context, args, map); | |||
break; | |||
case RunMode.Async: //Always async | |||
var t2 = Task.Run(() => _action(context, args, map)); | |||
break; | |||
} | |||
return ExecuteResult.FromSuccess(); | |||
} | |||
catch (Exception ex) | |||
{ | |||
return ExecuteResult.FromError(ex); | |||
} | |||
} | |||
private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList) | |||
{ | |||
int argCount = Parameters.Count; | |||
var array = new object[Parameters.Count]; | |||
if (HasVarArgs) | |||
argCount--; | |||
int i = 0; | |||
foreach (var arg in argList) | |||
{ | |||
if (i == argCount) | |||
throw new InvalidOperationException("Command was invoked with too many parameters"); | |||
array[i++] = arg; | |||
} | |||
if (i < argCount) | |||
throw new InvalidOperationException("Command was invoked with too few parameters"); | |||
if (HasVarArgs) | |||
{ | |||
var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t => | |||
{ | |||
var method = _convertParamsMethod.MakeGenericMethod(t); | |||
return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>)); | |||
}); | |||
array[i] = func(paramsList); | |||
} | |||
return array; | |||
} | |||
private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList) | |||
=> paramsList.Cast<T>().ToArray(); | |||
} | |||
} |
@@ -0,0 +1,96 @@ | |||
using System.Linq; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using Discord.Commands.Builders; | |||
namespace Discord.Commands | |||
{ | |||
public class ModuleInfo | |||
{ | |||
public CommandService Service { get; } | |||
public string Name { get; } | |||
public string Summary { get; } | |||
public string Remarks { get; } | |||
public IReadOnlyList<string> Aliases { get; } | |||
public IEnumerable<CommandInfo> Commands { get; } | |||
public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | |||
public IReadOnlyList<ModuleInfo> Submodules { get; } | |||
internal ModuleInfo(ModuleBuilder builder, CommandService service) | |||
{ | |||
Service = service; | |||
Name = builder.Name; | |||
Summary = builder.Summary; | |||
Remarks = builder.Remarks; | |||
Aliases = BuildAliases(builder).ToImmutableArray(); | |||
Commands = builder.Commands.Select(x => x.Build(this, service)); | |||
Preconditions = BuildPreconditions(builder).ToImmutableArray(); | |||
Submodules = BuildSubmodules(builder, service).ToImmutableArray(); | |||
} | |||
private static IEnumerable<string> BuildAliases(ModuleBuilder builder) | |||
{ | |||
IEnumerable<string> result = null; | |||
Stack<ModuleBuilder> builderStack = new Stack<ModuleBuilder>(); | |||
builderStack.Push(builder); | |||
ModuleBuilder parent = builder.Parent; | |||
while (parent != null) | |||
{ | |||
builderStack.Push(parent); | |||
parent = parent.Parent; | |||
} | |||
while (builderStack.Count() > 0) | |||
{ | |||
ModuleBuilder level = builderStack.Pop(); //get the topmost builder | |||
if (result == null) | |||
{ | |||
if (level.Aliases.Count > 0) | |||
result = level.Aliases.ToList(); //create a shallow copy so we don't overwrite the builder unexpectedly | |||
} | |||
else if (result.Count() > level.Aliases.Count) | |||
result = result.Permutate(level.Aliases, (first, second) => first + " " + second); | |||
else | |||
result = level.Aliases.Permutate(result, (second, first) => first + " " + second); | |||
} | |||
if (result == null) //there were no aliases; default to an empty list | |||
result = new List<string>(); | |||
return result; | |||
} | |||
private static List<ModuleInfo> BuildSubmodules(ModuleBuilder parent, CommandService service) | |||
{ | |||
var result = new List<ModuleInfo>(); | |||
foreach (var submodule in parent.Modules) | |||
{ | |||
result.Add(submodule.Build(service)); | |||
} | |||
return result; | |||
} | |||
private static List<PreconditionAttribute> BuildPreconditions(ModuleBuilder builder) | |||
{ | |||
var result = new List<PreconditionAttribute>(); | |||
ModuleBuilder parent = builder; | |||
while (parent != null) | |||
{ | |||
result.AddRange(parent.Preconditions); | |||
parent = parent.Parent; | |||
} | |||
return result; | |||
} | |||
} | |||
} |
@@ -1,37 +1,40 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using System.Reflection; | |||
using System; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Discord.Commands.Builders; | |||
namespace Discord.Commands | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class CommandParameter | |||
public class ParameterInfo | |||
{ | |||
private readonly TypeReader _reader; | |||
public ParameterInfo Source { get; } | |||
internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) | |||
{ | |||
Command = command; | |||
Name = builder.Name; | |||
Summary = builder.Summary; | |||
IsOptional = builder.IsOptional; | |||
IsRemainder = builder.IsRemainder; | |||
IsMultiple = builder.IsMultiple; | |||
Type = builder.ParameterType; | |||
DefaultValue = builder.DefaultValue; | |||
_reader = builder.TypeReader; | |||
} | |||
public CommandInfo Command { get; } | |||
public string Name { get; } | |||
public string Summary { get; } | |||
public bool IsOptional { get; } | |||
public bool IsRemainder { get; } | |||
public bool IsMultiple { get; } | |||
public Type ElementType { get; } | |||
public Type Type { get; } | |||
public object DefaultValue { get; } | |||
public CommandParameter(ParameterInfo source, string name, string summary, Type type, TypeReader reader, bool isOptional, bool isRemainder, bool isMultiple, object defaultValue) | |||
{ | |||
Source = source; | |||
Name = name; | |||
Summary = summary; | |||
ElementType = type; | |||
_reader = reader; | |||
IsOptional = isOptional; | |||
IsRemainder = isRemainder; | |||
IsMultiple = isMultiple; | |||
DefaultValue = defaultValue; | |||
} | |||
public async Task<TypeReaderResult> Parse(CommandContext context, string input) | |||
{ | |||
return await _reader.Read(context, input).ConfigureAwait(false); | |||
@@ -40,4 +43,4 @@ namespace Discord.Commands | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}"; | |||
} | |||
} | |||
} |
@@ -1,95 +1,39 @@ | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Collections.Generic; | |||
namespace Discord.Commands | |||
{ | |||
internal class CommandMap | |||
{ | |||
static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' }; | |||
private readonly object _lockObj = new object(); | |||
private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | |||
private readonly CommandMapNode _root; | |||
private static readonly string[] _blankAliases = new[] { "" }; | |||
public CommandMap() | |||
{ | |||
_nodes = new ConcurrentDictionary<string, CommandMapNode>(); | |||
_root = new CommandMapNode(""); | |||
} | |||
public void AddCommand(CommandInfo command) | |||
{ | |||
foreach (string text in command.Aliases) | |||
{ | |||
int nextSpace = NextWhitespace(text); | |||
string name; | |||
if (nextSpace == -1) | |||
name = text; | |||
else | |||
name = text.Substring(0, nextSpace); | |||
lock (_lockObj) | |||
{ | |||
var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(x)); | |||
nextNode.AddCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); | |||
} | |||
} | |||
foreach (string text in GetAliases(command)) | |||
_root.AddCommand(text, 0, command); | |||
} | |||
public void RemoveCommand(CommandInfo command) | |||
{ | |||
foreach (string text in command.Aliases) | |||
{ | |||
int nextSpace = NextWhitespace(text); | |||
string name; | |||
if (nextSpace == -1) | |||
name = text; | |||
else | |||
name = text.Substring(0, nextSpace); | |||
lock (_lockObj) | |||
{ | |||
CommandMapNode nextNode; | |||
if (_nodes.TryGetValue(name, out nextNode)) | |||
{ | |||
nextNode.RemoveCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); | |||
if (nextNode.IsEmpty) | |||
_nodes.TryRemove(name, out nextNode); | |||
} | |||
} | |||
} | |||
foreach (string text in GetAliases(command)) | |||
_root.RemoveCommand(text, 0, command); | |||
} | |||
public IEnumerable<CommandInfo> GetCommands(string text) | |||
{ | |||
int nextSpace = NextWhitespace(text); | |||
string name; | |||
if (nextSpace == -1) | |||
name = text; | |||
else | |||
name = text.Substring(0, nextSpace); | |||
lock (_lockObj) | |||
{ | |||
CommandMapNode nextNode; | |||
if (_nodes.TryGetValue(name, out nextNode)) | |||
return nextNode.GetCommands(text, nextSpace + 1); | |||
else | |||
return Enumerable.Empty<CommandInfo>(); | |||
} | |||
return _root.GetCommands(text, 0); | |||
} | |||
private static int NextWhitespace(string text) | |||
private IReadOnlyList<string> GetAliases(CommandInfo command) | |||
{ | |||
int lowest = int.MaxValue; | |||
for (int i = 0; i < _whitespaceChars.Length; i++) | |||
{ | |||
int index = text.IndexOf(_whitespaceChars[i]); | |||
if (index != -1 && index < lowest) | |||
lowest = index; | |||
} | |||
return (lowest != int.MaxValue) ? lowest : -1; | |||
var aliases = command.Aliases; | |||
if (aliases.Count == 0) | |||
return _blankAliases; | |||
return aliases; | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System.Collections.Concurrent; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -6,6 +7,8 @@ namespace Discord.Commands | |||
{ | |||
internal class CommandMapNode | |||
{ | |||
private static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' }; | |||
private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | |||
private readonly string _name; | |||
private readonly object _lockObj = new object(); | |||
@@ -22,13 +25,17 @@ namespace Discord.Commands | |||
public void AddCommand(string text, int index, CommandInfo command) | |||
{ | |||
int nextSpace = text.IndexOf(' ', index); | |||
int nextSpace = NextWhitespace(text, index); | |||
string name; | |||
lock (_lockObj) | |||
{ | |||
if (text == "") | |||
{ | |||
if (_name == "") | |||
throw new InvalidOperationException("Cannot add commands to the root node."); | |||
_commands = _commands.Add(command); | |||
} | |||
else | |||
{ | |||
if (nextSpace == -1) | |||
@@ -43,7 +50,7 @@ namespace Discord.Commands | |||
} | |||
public void RemoveCommand(string text, int index, CommandInfo command) | |||
{ | |||
int nextSpace = text.IndexOf(' ', index); | |||
int nextSpace = NextWhitespace(text, index); | |||
string name; | |||
lock (_lockObj) | |||
@@ -70,7 +77,7 @@ namespace Discord.Commands | |||
public IEnumerable<CommandInfo> GetCommands(string text, int index) | |||
{ | |||
int nextSpace = text.IndexOf(' ', index); | |||
int nextSpace = NextWhitespace(text, index); | |||
string name; | |||
var commands = _commands; | |||
@@ -92,5 +99,17 @@ namespace Discord.Commands | |||
} | |||
} | |||
} | |||
private static int NextWhitespace(string text, int startIndex) | |||
{ | |||
int lowest = int.MaxValue; | |||
for (int i = 0; i < _whitespaceChars.Length; i++) | |||
{ | |||
int index = text.IndexOf(_whitespaceChars[i], startIndex); | |||
if (index != -1 && index < lowest) | |||
lowest = index; | |||
} | |||
return (lowest != int.MaxValue) ? lowest : -1; | |||
} | |||
} | |||
} |
@@ -6,9 +6,9 @@ namespace Discord.Commands | |||
{ | |||
public CommandContext Context { get; internal set; } | |||
protected virtual async Task<IUserMessage> ReplyAsync(string message, bool isTTS = false, RequestOptions options = null) | |||
protected virtual async Task<IUserMessage> ReplyAsync(string message, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) | |||
{ | |||
return await Context.Channel.SendMessageAsync(message, isTTS, options).ConfigureAwait(false); | |||
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); | |||
} | |||
} | |||
} |
@@ -1,85 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using System.Reflection; | |||
namespace Discord.Commands | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class ModuleInfo | |||
{ | |||
internal readonly Func<IDependencyMap, ModuleBase> _builder; | |||
public TypeInfo Source { get; } | |||
public CommandService Service { get; } | |||
public string Name { get; } | |||
public string Prefix { get; } | |||
public string Summary { get; } | |||
public string Remarks { get; } | |||
public IEnumerable<CommandInfo> Commands { get; } | |||
public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | |||
internal ModuleInfo(TypeInfo source, CommandService service) | |||
{ | |||
Source = source; | |||
Service = service; | |||
Name = source.Name; | |||
_builder = ReflectionUtils.CreateBuilder<ModuleBase>(source, Service); | |||
var groupAttr = source.GetCustomAttribute<GroupAttribute>(); | |||
if (groupAttr != null) | |||
Prefix = groupAttr.Prefix; | |||
else | |||
Prefix = ""; | |||
var nameAttr = source.GetCustomAttribute<NameAttribute>(); | |||
if (nameAttr != null) | |||
Name = nameAttr.Text; | |||
var summaryAttr = source.GetCustomAttribute<SummaryAttribute>(); | |||
if (summaryAttr != null) | |||
Summary = summaryAttr.Text; | |||
var remarksAttr = source.GetCustomAttribute<RemarksAttribute>(); | |||
if (remarksAttr != null) | |||
Remarks = remarksAttr.Text; | |||
List<CommandInfo> commands = new List<CommandInfo>(); | |||
SearchClass(source, commands, Prefix); | |||
Commands = commands; | |||
Preconditions = Source.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray(); | |||
} | |||
private void SearchClass(TypeInfo parentType, List<CommandInfo> commands, string groupPrefix) | |||
{ | |||
foreach (var method in parentType.DeclaredMethods) | |||
{ | |||
var cmdAttr = method.GetCustomAttribute<CommandAttribute>(); | |||
if (cmdAttr != null) | |||
commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix)); | |||
} | |||
foreach (var type in parentType.DeclaredNestedTypes) | |||
{ | |||
var groupAttrib = type.GetCustomAttribute<GroupAttribute>(); | |||
if (groupAttrib != null) | |||
{ | |||
string nextGroupPrefix; | |||
if (groupPrefix != "") | |||
nextGroupPrefix = groupPrefix + " " + (groupAttrib.Prefix ?? type.Name.ToLowerInvariant()); | |||
else | |||
nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); | |||
SearchClass(type, commands, nextGroupPrefix); | |||
} | |||
} | |||
} | |||
internal ModuleBase CreateInstance(IDependencyMap map) | |||
=> _builder(map); | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => Name; | |||
} | |||
} |
@@ -31,11 +31,13 @@ namespace Discord.Commands | |||
var byNameBuilder = ImmutableDictionary.CreateBuilder<string, object>(); | |||
var byValueBuilder = ImmutableDictionary.CreateBuilder<T, object>(); | |||
foreach (var v in Enum.GetValues(_enumType)) | |||
{ | |||
byNameBuilder.Add(v.ToString().ToLower(), v); | |||
byValueBuilder.Add((T)v, v); | |||
foreach (var v in Enum.GetNames(_enumType)) | |||
{ | |||
var parsedValue = Enum.Parse(_enumType, v); | |||
byNameBuilder.Add(v.ToLower(), parsedValue); | |||
if (!byValueBuilder.ContainsKey((T)parsedValue)) | |||
byValueBuilder.Add((T)parsedValue, parsedValue); | |||
} | |||
_enumsByName = byNameBuilder.ToImmutable(); | |||
@@ -2,6 +2,7 @@ | |||
{ | |||
public enum RunMode | |||
{ | |||
Default, | |||
Sync, | |||
Mixed, | |||
Async | |||
@@ -18,7 +18,7 @@ namespace Discord.Commands | |||
throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); | |||
var constructor = constructors[0]; | |||
ParameterInfo[] parameters = constructor.GetParameters(); | |||
System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); | |||
return (map) => | |||
{ |
@@ -1,5 +1,5 @@ | |||
{ | |||
"version": "1.0.0-beta2-*", | |||
"version": "1.0.0-*", | |||
"description": "A Discord.Net extension adding support for bot commands.", | |||
"authors": [ "RogueException" ], | |||
@@ -40,4 +40,4 @@ | |||
] | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
#pragma warning disable CS1591 | |||
using System; | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
@@ -13,9 +14,23 @@ namespace Discord.API | |||
public string Description { get; set; } | |||
[JsonProperty("url")] | |||
public string Url { get; set; } | |||
[JsonProperty("color")] | |||
public uint? Color { get; set; } | |||
[JsonProperty("timestamp")] | |||
public DateTimeOffset? Timestamp { get; set; } | |||
[JsonProperty("author")] | |||
public Optional<EmbedAuthor> Author { get; set; } | |||
[JsonProperty("footer")] | |||
public Optional<EmbedFooter> Footer { get; set; } | |||
[JsonProperty("video")] | |||
public Optional<EmbedVideo> Video { get; set; } | |||
[JsonProperty("thumbnail")] | |||
public Optional<EmbedThumbnail> Thumbnail { get; set; } | |||
[JsonProperty("image")] | |||
public Optional<EmbedImage> Image { get; set; } | |||
[JsonProperty("provider")] | |||
public Optional<EmbedProvider> Provider { get; set; } | |||
[JsonProperty("fields")] | |||
public Optional<EmbedField[]> Fields { get; set; } | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
public class EmbedAuthor | |||
{ | |||
[JsonProperty("name")] | |||
public string Name { get; set; } | |||
[JsonProperty("url")] | |||
public string Url { get; set; } | |||
[JsonProperty("icon_url")] | |||
public string IconUrl { get; set; } | |||
[JsonProperty("proxy_icon_url")] | |||
public string ProxyIconUrl { get; set; } | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
public class EmbedField | |||
{ | |||
[JsonProperty("name")] | |||
public string Name { get; set; } | |||
[JsonProperty("value")] | |||
public string Value { get; set; } | |||
[JsonProperty("inline")] | |||
public bool Inline { get; set; } | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
public class EmbedFooter | |||
{ | |||
[JsonProperty("text")] | |||
public string Text { get; set; } | |||
[JsonProperty("icon_url")] | |||
public string IconUrl { get; set; } | |||
[JsonProperty("proxy_icon_url")] | |||
public string ProxyIconUrl { get; set; } | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
public class EmbedImage | |||
{ | |||
[JsonProperty("url")] | |||
public string Url { get; set; } | |||
[JsonProperty("proxy_url")] | |||
public string ProxyUrl { get; set; } | |||
[JsonProperty("height")] | |||
public Optional<int> Height { get; set; } | |||
[JsonProperty("width")] | |||
public Optional<int> Width { get; set; } | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
public class EmbedVideo | |||
{ | |||
[JsonProperty("url")] | |||
public string Url { get; set; } | |||
[JsonProperty("height")] | |||
public Optional<int> Height { get; set; } | |||
[JsonProperty("width")] | |||
public Optional<int> Width { get; set; } | |||
} | |||
} |
@@ -49,7 +49,7 @@ namespace Discord.API | |||
{ | |||
_restClientProvider = restClientProvider; | |||
_userAgent = userAgent; | |||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||
_serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() }; | |||
RequestQueue = requestQueue; | |||
FetchCurrentUser = true; | |||
@@ -165,30 +165,30 @@ namespace Discord.API | |||
//Core | |||
internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids, | |||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | |||
public async Task SendAsync(string method, string endpoint, | |||
string bucketId = null, string clientBucketId = null, RequestOptions options = null) | |||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) | |||
{ | |||
options = options ?? new RequestOptions(); | |||
options.HeaderOnly = true; | |||
options.BucketId = bucketId; | |||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; | |||
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | |||
options.IsClientBucket = AuthTokenType == TokenType.User; | |||
var request = new RestRequest(_restClient, method, endpoint, options); | |||
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); | |||
} | |||
internal Task SendJsonAsync(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids, | |||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | |||
public async Task SendJsonAsync(string method, string endpoint, object payload, | |||
string bucketId = null, string clientBucketId = null, RequestOptions options = null) | |||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) | |||
{ | |||
options = options ?? new RequestOptions(); | |||
options.HeaderOnly = true; | |||
options.BucketId = bucketId; | |||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; | |||
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | |||
options.IsClientBucket = AuthTokenType == TokenType.User; | |||
var json = payload != null ? SerializeJson(payload) : null; | |||
var request = new JsonRestRequest(_restClient, method, endpoint, json, options); | |||
@@ -196,43 +196,43 @@ namespace Discord.API | |||
} | |||
internal Task SendMultipartAsync(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | |||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | |||
public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, | |||
string bucketId = null, string clientBucketId = null, RequestOptions options = null) | |||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) | |||
{ | |||
options = options ?? new RequestOptions(); | |||
options.HeaderOnly = true; | |||
options.BucketId = bucketId; | |||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; | |||
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | |||
options.IsClientBucket = AuthTokenType == TokenType.User; | |||
var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); | |||
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); | |||
} | |||
internal Task<TResponse> SendAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, BucketIds ids, | |||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class | |||
=> SendAsync<TResponse>(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class | |||
=> SendAsync<TResponse>(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | |||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint, | |||
string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class | |||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class | |||
{ | |||
options = options ?? new RequestOptions(); | |||
options.BucketId = bucketId; | |||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; | |||
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | |||
options.IsClientBucket = AuthTokenType == TokenType.User; | |||
var request = new RestRequest(_restClient, method, endpoint, options); | |||
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); | |||
} | |||
internal Task<TResponse> SendJsonAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids, | |||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class | |||
=> SendJsonAsync<TResponse>(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class | |||
=> SendJsonAsync<TResponse>(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | |||
public async Task<TResponse> SendJsonAsync<TResponse>(string method, string endpoint, object payload, | |||
string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class | |||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class | |||
{ | |||
options = options ?? new RequestOptions(); | |||
options.BucketId = bucketId; | |||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; | |||
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | |||
options.IsClientBucket = AuthTokenType == TokenType.User; | |||
var json = payload != null ? SerializeJson(payload) : null; | |||
var request = new JsonRestRequest(_restClient, method, endpoint, json, options); | |||
@@ -240,14 +240,14 @@ namespace Discord.API | |||
} | |||
internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | |||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | |||
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, | |||
string bucketId = null, string clientBucketId = null, RequestOptions options = null) | |||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) | |||
{ | |||
options = options ?? new RequestOptions(); | |||
options.BucketId = bucketId; | |||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; | |||
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | |||
options.IsClientBucket = AuthTokenType == TokenType.User; | |||
var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); | |||
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); | |||
@@ -432,20 +432,22 @@ namespace Discord.API | |||
if (relativeId != null) | |||
endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; | |||
else | |||
endpoint = () =>$"channels/{channelId}/messages?limit={limit}"; | |||
endpoint = () => $"channels/{channelId}/messages?limit={limit}"; | |||
return await SendAsync<IReadOnlyCollection<Message>>("GET", endpoint, ids, options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Message> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
Preconditions.NotNull(args, nameof(args)); | |||
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||
if (!args.Embed.IsSpecified || args.Embed.Value == null) | |||
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||
if (args.Content.Length > DiscordConfig.MaxMessageSize) | |||
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(channelId: channelId); | |||
return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", args, ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false); | |||
return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) | |||
{ | |||
@@ -464,7 +466,7 @@ namespace Discord.API | |||
} | |||
var ids = new BucketIds(channelId: channelId); | |||
return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false); | |||
return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
} | |||
public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||
{ | |||
@@ -496,21 +498,22 @@ namespace Discord.API | |||
break; | |||
} | |||
} | |||
public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) | |||
public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
Preconditions.NotNull(args, nameof(args)); | |||
if (args.Content.IsSpecified) | |||
{ | |||
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||
if (!args.Embed.IsSpecified) | |||
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||
if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) | |||
throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||
} | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(channelId: channelId); | |||
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false); | |||
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
} | |||
public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) | |||
{ | |||
@@ -1112,19 +1115,6 @@ namespace Discord.API | |||
using (JsonReader reader = new JsonTextReader(text)) | |||
return _serializer.Deserialize<T>(reader); | |||
} | |||
internal string GetBucketId(ulong guildId = 0, ulong channelId = 0, [CallerMemberName] string methodName = "") | |||
{ | |||
if (guildId != 0) | |||
{ | |||
if (channelId != 0) | |||
return $"{methodName}({guildId}/{channelId})"; | |||
else | |||
return $"{methodName}({guildId})"; | |||
} | |||
else if (channelId != 0) | |||
return $"{methodName}({channelId})"; | |||
return $"{methodName}()"; | |||
} | |||
internal class BucketIds | |||
{ | |||
@@ -1,4 +1,5 @@ | |||
#pragma warning disable CS1591 | |||
using System; | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Rest | |||
@@ -13,6 +14,8 @@ namespace Discord.API.Rest | |||
public Optional<string> Nonce { get; set; } | |||
[JsonProperty("tts")] | |||
public Optional<bool> IsTTS { get; set; } | |||
[JsonProperty("embed")] | |||
public Optional<Embed> Embed { get; set; } | |||
public CreateMessageParams(string content) | |||
{ | |||
@@ -8,5 +8,7 @@ namespace Discord.API.Rest | |||
{ | |||
[JsonProperty("content")] | |||
public Optional<string> Content { get; set; } | |||
[JsonProperty("embed")] | |||
public Optional<Embed> Embed { get; set; } | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" /> | |||
<PropertyGroup> | |||
<Description>A .Net API wrapper and bot framework for Discord.</Description> | |||
<VersionPrefix>1.0.0-beta2</VersionPrefix> | |||
<TargetFramework>netstandard1.3</TargetFramework> | |||
<AssemblyName>Discord.Net.Core</AssemblyName> | |||
<PackageTags>discord;discordapp</PackageTags> | |||
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | |||
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | |||
<RepositoryType>git</RepositoryType> | |||
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl> | |||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Compile Include="**\*.cs" /> | |||
<EmbeddedResource Include="**\*.resx" /> | |||
<EmbeddedResource Include="compiler\resources\**\*" /> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.NET.Sdk"> | |||
<Version>1.0.0-alpha-20161104-2</Version> | |||
<PrivateAssets>All</PrivateAssets> | |||
</PackageReference> | |||
<PackageReference Include="Microsoft.Win32.Primitives"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="Newtonsoft.Json"> | |||
<Version>9.0.1</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Collections.Concurrent"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Collections.Immutable"> | |||
<Version>1.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Interactive.Async"> | |||
<Version>3.1.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Net.Http"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Net.WebSockets.Client"> | |||
<Version>4.3.0</Version> | |||
<PrivateAssets>All</PrivateAssets> | |||
</PackageReference> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<PropertyGroup Label="Configuration"> | |||
<SignAssembly>False</SignAssembly> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
</Project> |
@@ -1,19 +0,0 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||
<PropertyGroup Label="Globals"> | |||
<ProjectGuid>91e9e7bd-75c9-4e98-84aa-2c271922e5c2</ProjectGuid> | |||
<RootNamespace>Discord</RootNamespace> | |||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<SchemaVersion>2.0</SchemaVersion> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||
</Project> |
@@ -11,9 +11,10 @@ namespace Discord | |||
"Unknown"; | |||
public static readonly string ClientAPIUrl = $"https://discordapp.com/api/v{APIVersion}/"; | |||
public const string CDNUrl = "https://discordcdn.com/"; | |||
public const string CDNUrl = "https://cdn.discordapp.com/"; | |||
public const string InviteUrl = "https://discord.gg/"; | |||
public const int DefaultRequestTimeout = 15000; | |||
public const int MaxMessageSize = 2000; | |||
public const int MaxMessagesPerBatch = 100; | |||
public const int MaxUsersPerBatch = 1000; | |||
@@ -8,7 +8,7 @@ namespace Discord | |||
public interface IMessageChannel : IChannel | |||
{ | |||
/// <summary> Sends a message to this message channel. </summary> | |||
Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null); | |||
Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null); | |||
/// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); | |||
/// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||
@@ -0,0 +1,29 @@ | |||
using System.Diagnostics; | |||
using Model = Discord.API.EmbedAuthor; | |||
namespace Discord | |||
{ | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
public struct EmbedAuthor | |||
{ | |||
public string Name { get; set; } | |||
public string Url { get; set; } | |||
public string IconUrl { get; set; } | |||
public string ProxyIconUrl { get; set; } | |||
private EmbedAuthor(string name, string url, string iconUrl, string proxyIconUrl) | |||
{ | |||
Name = name; | |||
Url = url; | |||
IconUrl = iconUrl; | |||
ProxyIconUrl = proxyIconUrl; | |||
} | |||
internal static EmbedAuthor Create(Model model) | |||
{ | |||
return new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl); | |||
} | |||
private string DebuggerDisplay => $"{Name} ({Url})"; | |||
public override string ToString() => Name; | |||
} | |||
} |
@@ -0,0 +1,208 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Embed = Discord.API.Embed; | |||
using Field = Discord.API.EmbedField; | |||
using Author = Discord.API.EmbedAuthor; | |||
using Footer = Discord.API.EmbedFooter; | |||
using Thumbnail = Discord.API.EmbedThumbnail; | |||
using Image = Discord.API.EmbedImage; | |||
namespace Discord | |||
{ | |||
public class EmbedBuilder | |||
{ | |||
private readonly Embed _model; | |||
private readonly List<Field> _fields; | |||
public EmbedBuilder() | |||
{ | |||
_model = new Embed { Type = "rich" }; | |||
_fields = new List<Field>(); | |||
} | |||
public string Title { get { return _model.Title; } set { _model.Title = value; } } | |||
public string Description { get { return _model.Description; } set { _model.Description = value; } } | |||
public string Url { get { return _model.Url; } set { _model.Url = value; } } | |||
public string ThumbnailUrl { get; set; } | |||
public string ImageUrl { get; set; } | |||
public DateTimeOffset? Timestamp { get; set; } | |||
public Color? Color { get { return _model.Color.HasValue ? new Color(_model.Color.Value) : (Color?)null; } set { _model.Color = value?.RawValue; } } | |||
public EmbedAuthorBuilder Author { get; set; } | |||
public EmbedFooterBuilder Footer { get; set; } | |||
public EmbedBuilder WithTitle(string title) | |||
{ | |||
Title = title; | |||
return this; | |||
} | |||
public EmbedBuilder WithDescription(string description) | |||
{ | |||
Description = description; | |||
return this; | |||
} | |||
public EmbedBuilder WithUrl(string url) | |||
{ | |||
Url = url; | |||
return this; | |||
} | |||
public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) | |||
{ | |||
ThumbnailUrl = thumbnailUrl; | |||
return this; | |||
} | |||
public EmbedBuilder WithImageUrl(string imageUrl) | |||
{ | |||
ImageUrl = ImageUrl; | |||
return this; | |||
} | |||
public EmbedBuilder WithCurrentTimestamp() | |||
{ | |||
Timestamp = DateTimeOffset.UtcNow; | |||
return this; | |||
} | |||
public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset) | |||
{ | |||
Timestamp = dateTimeOffset; | |||
return this; | |||
} | |||
public EmbedBuilder WithColor(Color color) | |||
{ | |||
Color = color; | |||
return this; | |||
} | |||
public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) | |||
{ | |||
Author = author; | |||
return this; | |||
} | |||
public EmbedBuilder WithAuthor(Action<EmbedAuthorBuilder> action) | |||
{ | |||
var author = new EmbedAuthorBuilder(); | |||
action(author); | |||
Author = author; | |||
return this; | |||
} | |||
public EmbedBuilder WithFooter(EmbedFooterBuilder footer) | |||
{ | |||
Footer = footer; | |||
return this; | |||
} | |||
public EmbedBuilder WithFooter(Action<EmbedFooterBuilder> action) | |||
{ | |||
var footer = new EmbedFooterBuilder(); | |||
action(footer); | |||
Footer = footer; | |||
return this; | |||
} | |||
public EmbedBuilder AddField(Action<EmbedFieldBuilder> action) | |||
{ | |||
var field = new EmbedFieldBuilder(); | |||
action(field); | |||
_fields.Add(field.ToModel()); | |||
return this; | |||
} | |||
internal Embed Build() | |||
{ | |||
_model.Author = Author?.ToModel(); | |||
_model.Footer = Footer?.ToModel(); | |||
_model.Timestamp = Timestamp?.ToUniversalTime(); | |||
_model.Thumbnail = ThumbnailUrl != null ? new Thumbnail { Url = ThumbnailUrl } : null; | |||
_model.Image = ImageUrl != null ? new Image { Url = ImageUrl } : null; | |||
_model.Fields = _fields.ToArray(); | |||
return _model; | |||
} | |||
} | |||
public class EmbedFieldBuilder | |||
{ | |||
private readonly Field _model; | |||
public string Name { get { return _model.Name; } set { _model.Name = value; } } | |||
public string Value { get { return _model.Value; } set { _model.Value = value; } } | |||
public bool IsInline { get { return _model.Inline; } set { _model.Inline = value; } } | |||
public EmbedFieldBuilder() | |||
{ | |||
_model = new Field(); | |||
} | |||
public EmbedFieldBuilder WithName(string name) | |||
{ | |||
Name = name; | |||
return this; | |||
} | |||
public EmbedFieldBuilder WithValue(string value) | |||
{ | |||
Value = value; | |||
return this; | |||
} | |||
public EmbedFieldBuilder WithIsInline(bool isInline) | |||
{ | |||
IsInline = isInline; | |||
return this; | |||
} | |||
internal Field ToModel() => _model; | |||
} | |||
public class EmbedAuthorBuilder | |||
{ | |||
private readonly Author _model; | |||
public string Name { get { return _model.Name; } set { _model.Name = value; } } | |||
public string Url { get { return _model.Url; } set { _model.Url = value; } } | |||
public string IconUrl { get { return _model.IconUrl; } set { _model.IconUrl = value; } } | |||
public EmbedAuthorBuilder() | |||
{ | |||
_model = new Author(); | |||
} | |||
public EmbedAuthorBuilder WithName(string name) | |||
{ | |||
Name = name; | |||
return this; | |||
} | |||
public EmbedAuthorBuilder WithUrl(string url) | |||
{ | |||
Url = url; | |||
return this; | |||
} | |||
public EmbedAuthorBuilder WithIconUrl(string iconUrl) | |||
{ | |||
IconUrl = iconUrl; | |||
return this; | |||
} | |||
internal Author ToModel() => _model; | |||
} | |||
public class EmbedFooterBuilder | |||
{ | |||
private readonly Footer _model; | |||
public string Text { get { return _model.Text; } set { _model.Text = value; } } | |||
public string IconUrl { get { return _model.IconUrl; } set { _model.IconUrl = value; } } | |||
public EmbedFooterBuilder() | |||
{ | |||
_model = new Footer(); | |||
} | |||
public EmbedFooterBuilder WithText(string text) | |||
{ | |||
Text = text; | |||
return this; | |||
} | |||
public EmbedFooterBuilder WithIconUrl(string iconUrl) | |||
{ | |||
IconUrl = iconUrl; | |||
return this; | |||
} | |||
internal Footer ToModel() => _model; | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
using System.Diagnostics; | |||
using Model = Discord.API.EmbedField; | |||
namespace Discord | |||
{ | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
public struct EmbedField | |||
{ | |||
public string Name { get; set; } | |||
public string Value { get; set; } | |||
public bool Inline { get; set; } | |||
private EmbedField(string name, string value, bool inline) | |||
{ | |||
Name = name; | |||
Value = value; | |||
Inline = inline; | |||
} | |||
internal static EmbedField Create(Model model) | |||
{ | |||
return new EmbedField(model.Name, model.Value, model.Inline); | |||
} | |||
private string DebuggerDisplay => $"{Name} ({Value}"; | |||
public override string ToString() => Name; | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
using System.Diagnostics; | |||
using Model = Discord.API.EmbedFooter; | |||
namespace Discord | |||
{ | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
public struct EmbedFooter | |||
{ | |||
public string Text { get; set; } | |||
public string IconUrl { get; set; } | |||
public string ProxyUrl { get; set; } | |||
private EmbedFooter(string text, string iconUrl, string proxyUrl) | |||
{ | |||
Text = text; | |||
IconUrl = iconUrl; | |||
ProxyUrl = proxyUrl; | |||
} | |||
internal static EmbedFooter Create(Model model) | |||
{ | |||
return new EmbedFooter(model.Text, model.IconUrl, model.ProxyIconUrl); | |||
} | |||
private string DebuggerDisplay => $"{Text} ({IconUrl})"; | |||
public override string ToString() => Text; | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
using System.Diagnostics; | |||
using Model = Discord.API.EmbedImage; | |||
namespace Discord | |||
{ | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
public struct EmbedImage | |||
{ | |||
public string Url { get; } | |||
public string ProxyUrl { get; } | |||
public int? Height { get; } | |||
public int? Width { get; } | |||
private EmbedImage(string url, string proxyUrl, int? height, int? width) | |||
{ | |||
Url = url; | |||
ProxyUrl = proxyUrl; | |||
Height = height; | |||
Width = width; | |||
} | |||
internal static EmbedImage Create(Model model) | |||
{ | |||
return new EmbedImage(model.Url, model.ProxyUrl, | |||
model.Height.IsSpecified ? model.Height.Value : (int?)null, | |||
model.Width.IsSpecified ? model.Width.Value : (int?)null); | |||
} | |||
private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; | |||
public override string ToString() => Url; | |||
} | |||
} |
@@ -25,7 +25,7 @@ namespace Discord | |||
model.Width.IsSpecified ? model.Width.Value : (int?)null); | |||
} | |||
private string DebuggerDisplay => $"{ToString()} ({Url})"; | |||
public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0"; | |||
private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; | |||
public override string ToString() => Url; | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
using System.Diagnostics; | |||
using Model = Discord.API.EmbedVideo; | |||
namespace Discord | |||
{ | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
public struct EmbedVideo | |||
{ | |||
public string Url { get; } | |||
public int? Height { get; } | |||
public int? Width { get; } | |||
private EmbedVideo(string url, int? height, int? width) | |||
{ | |||
Url = url; | |||
Height = height; | |||
Width = width; | |||
} | |||
internal static EmbedVideo Create(Model model) | |||
{ | |||
return new EmbedVideo(model.Url, | |||
model.Height.IsSpecified ? model.Height.Value : (int?)null, | |||
model.Width.IsSpecified ? model.Width.Value : (int?)null); | |||
} | |||
private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; | |||
public override string ToString() => Url; | |||
} | |||
} |
@@ -1,4 +1,7 @@ | |||
namespace Discord | |||
using System; | |||
using System.Collections.Immutable; | |||
namespace Discord | |||
{ | |||
public interface IEmbed | |||
{ | |||
@@ -6,7 +9,14 @@ | |||
string Type { get; } | |||
string Title { get; } | |||
string Description { get; } | |||
DateTimeOffset? Timestamp { get; } | |||
Color? Color { get; } | |||
EmbedImage? Image { get; } | |||
EmbedVideo? Video { get; } | |||
EmbedAuthor? Author { get; } | |||
EmbedFooter? Footer { get; } | |||
EmbedProvider? Provider { get; } | |||
EmbedThumbnail? Thumbnail { get; } | |||
ImmutableArray<EmbedField> Fields { get; } | |||
} | |||
} |
@@ -1,5 +1,4 @@ | |||
using Discord.API.Rest; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
@@ -0,0 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace Discord | |||
{ | |||
public class ModifyMessageParams | |||
{ | |||
public Optional<string> Content { get; set; } | |||
public Optional<EmbedBuilder> Embed { get; set; } | |||
} | |||
} |
@@ -2,10 +2,12 @@ | |||
{ | |||
public enum TagHandling | |||
{ | |||
Ignore = 0, | |||
Remove, | |||
Name, | |||
FullName, | |||
Sanitize | |||
Ignore = 0, //<@53905483156684800> -> <@53905483156684800> | |||
Remove, //<@53905483156684800> -> | |||
Name, //<@53905483156684800> -> @Voltana | |||
NameNoPrefix, //<@53905483156684800> -> Voltana | |||
FullName, //<@53905483156684800> -> @Voltana#8252 | |||
FullNameNoPrefix, //<@53905483156684800> -> Voltana#8252 | |||
Sanitize //<@53905483156684800> -> <@53905483156684800> (w/ nbsp) | |||
} | |||
} |
@@ -32,6 +32,12 @@ namespace Discord | |||
} | |||
public Color(float r, float g, float b) | |||
{ | |||
if (r < 0.0f || r > 1.0f) | |||
throw new ArgumentOutOfRangeException(nameof(r), "A float value must be within [0,1]"); | |||
if (g < 0.0f || g > 1.0f) | |||
throw new ArgumentOutOfRangeException(nameof(g), "A float value must be within [0,1]"); | |||
if (b < 0.0f || b > 1.0f) | |||
throw new ArgumentOutOfRangeException(nameof(b), "A float value must be within [0,1]"); | |||
RawValue = | |||
((uint)(r * 255.0f) << 16) | | |||
((uint)(g * 255.0f) << 8) | | |||
@@ -4,7 +4,7 @@ using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public interface IRole : ISnowflakeEntity, IDeletable, IMentionable | |||
public interface IRole : ISnowflakeEntity, IDeletable, IMentionable, IComparable<IRole> | |||
{ | |||
/// <summary> Gets the guild owning this role.</summary> | |||
IGuild Guild { get; } | |||
@@ -27,4 +27,4 @@ namespace Discord | |||
///// <summary> Modifies this role. </summary> | |||
Task ModifyAsync(Action<ModifyGuildRoleParams> func, RequestOptions options = null); | |||
} | |||
} | |||
} |
@@ -6,14 +6,23 @@ namespace Discord | |||
{ | |||
public static class GuildUserExtensions | |||
{ | |||
//TODO: Should we remove Add/Remove? Encourages race conditions. | |||
public static Task AddRolesAsync(this IGuildUser user, params IRole[] roles) | |||
=> AddRolesAsync(user, (IEnumerable<IRole>)roles); | |||
=> ChangeRolesAsync(user, add: roles); | |||
public static Task AddRolesAsync(this IGuildUser user, IEnumerable<IRole> roles) | |||
=> user.ModifyAsync(x => x.RoleIds = user.RoleIds.Concat(roles.Select(y => y.Id)).ToArray()); | |||
=> ChangeRolesAsync(user, add: roles); | |||
public static Task RemoveRolesAsync(this IGuildUser user, params IRole[] roles) | |||
=> RemoveRolesAsync(user, (IEnumerable<IRole>)roles); | |||
=> ChangeRolesAsync(user, remove: roles); | |||
public static Task RemoveRolesAsync(this IGuildUser user, IEnumerable<IRole> roles) | |||
=> user.ModifyAsync(x => x.RoleIds = user.RoleIds.Except(roles.Select(y => y.Id)).ToArray()); | |||
=> ChangeRolesAsync(user, remove: roles); | |||
public static async Task ChangeRolesAsync(this IGuildUser user, IEnumerable<IRole> add = null, IEnumerable<IRole> remove = null) | |||
{ | |||
IEnumerable<ulong> roleIds = user.RoleIds; | |||
if (remove != null) | |||
roleIds = roleIds.Except(remove.Select(x => x.Id)); | |||
if (add != null) | |||
roleIds = roleIds.Concat(add.Select(x => x.Id)); | |||
await user.ModifyAsync(x => x.RoleIds = roleIds.ToArray()).ConfigureAwait(false); | |||
} | |||
} | |||
} |
@@ -0,0 +1,18 @@ | |||
namespace Discord | |||
{ | |||
internal static class RoleExtensions | |||
{ | |||
internal static int Compare(this IRole left, IRole right) | |||
{ | |||
if (left == null) | |||
return -1; | |||
if (right == null) | |||
return 1; | |||
var result = left.Position.CompareTo(right.Position); | |||
// As per Discord's documentation, a tie is broken by ID | |||
if (result != 0) | |||
return result; | |||
return left.Id.CompareTo(right.Id); | |||
} | |||
} | |||
} |
@@ -2,25 +2,47 @@ | |||
namespace Discord.Net.Queue | |||
{ | |||
public struct ClientBucket | |||
public enum ClientBucketType | |||
{ | |||
public const string SendEditId = "<send_edit>"; | |||
Unbucketed = 0, | |||
SendEdit = 1 | |||
} | |||
internal struct ClientBucket | |||
{ | |||
private static readonly ImmutableDictionary<ClientBucketType, ClientBucket> _defsByType; | |||
private static readonly ImmutableDictionary<string, ClientBucket> _defsById; | |||
private static readonly ImmutableDictionary<string, ClientBucket> _defs; | |||
static ClientBucket() | |||
{ | |||
var builder = ImmutableDictionary.CreateBuilder<string, ClientBucket>(); | |||
builder.Add(SendEditId, new ClientBucket(10, 10)); | |||
_defs = builder.ToImmutable(); | |||
} | |||
var buckets = new[] | |||
{ | |||
new ClientBucket(ClientBucketType.Unbucketed, "<unbucketed>", 10, 10), | |||
new ClientBucket(ClientBucketType.SendEdit, "<send_edit>", 10, 10) | |||
}; | |||
public static ClientBucket Get(string id) =>_defs[id]; | |||
var builder = ImmutableDictionary.CreateBuilder<ClientBucketType, ClientBucket>(); | |||
foreach (var bucket in buckets) | |||
builder.Add(bucket.Type, bucket); | |||
_defsByType = builder.ToImmutable(); | |||
var builder2 = ImmutableDictionary.CreateBuilder<string, ClientBucket>(); | |||
foreach (var bucket in buckets) | |||
builder2.Add(bucket.Id, bucket); | |||
_defsById = builder2.ToImmutable(); | |||
} | |||
public static ClientBucket Get(ClientBucketType type) => _defsByType[type]; | |||
public static ClientBucket Get(string id) => _defsById[id]; | |||
public ClientBucketType Type { get; } | |||
public string Id { get; } | |||
public int WindowCount { get; } | |||
public int WindowSeconds { get; } | |||
public ClientBucket(int count, int seconds) | |||
public ClientBucket(ClientBucketType type, string id, int count, int seconds) | |||
{ | |||
Type = type; | |||
Id = id; | |||
WindowCount = count; | |||
WindowSeconds = seconds; | |||
} | |||
@@ -79,7 +79,9 @@ namespace Discord.Net.Queue | |||
int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds); | |||
if (millis > 0) | |||
{ | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]"); | |||
#endif | |||
await Task.Delay(millis).ConfigureAwait(false); | |||
} | |||
} | |||
@@ -1,7 +1,10 @@ | |||
using Newtonsoft.Json; | |||
using Discord.Net.Rest; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
#if DEBUG_LIMITS | |||
using System.Diagnostics; | |||
#endif | |||
using System.IO; | |||
using System.Net; | |||
using System.Threading; | |||
@@ -27,8 +30,8 @@ namespace Discord.Net.Queue | |||
_lock = new object(); | |||
if (request.Options.ClientBucketId != null) | |||
WindowCount = ClientBucket.Get(request.Options.ClientBucketId).WindowCount; | |||
if (request.Options.IsClientBucket) | |||
WindowCount = ClientBucket.Get(request.Options.BucketId).WindowCount; | |||
else | |||
WindowCount = 1; //Only allow one request until we get a header back | |||
_semaphore = WindowCount; | |||
@@ -40,62 +43,91 @@ namespace Discord.Net.Queue | |||
public async Task<Stream> SendAsync(RestRequest request) | |||
{ | |||
int id = Interlocked.Increment(ref nextId); | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Start"); | |||
#endif | |||
LastAttemptAt = DateTimeOffset.UtcNow; | |||
while (true) | |||
{ | |||
await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false); | |||
await EnterAsync(id, request).ConfigureAwait(false); | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Sending..."); | |||
var response = await request.SendAsync().ConfigureAwait(false); | |||
TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]); | |||
var info = new RateLimitInfo(response.Headers); | |||
if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) | |||
#endif | |||
TimeSpan lag = default(TimeSpan); | |||
RateLimitInfo info = default(RateLimitInfo); | |||
try | |||
{ | |||
switch (response.StatusCode) | |||
var response = await request.SendAsync().ConfigureAwait(false); | |||
lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]); | |||
info = new RateLimitInfo(response.Headers); | |||
if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) | |||
{ | |||
case (HttpStatusCode)429: | |||
if (info.IsGlobal) | |||
{ | |||
Debug.WriteLine($"[{id}] (!) 429 [Global]"); | |||
_queue.PauseGlobal(info, lag); | |||
} | |||
else | |||
{ | |||
Debug.WriteLine($"[{id}] (!) 429"); | |||
UpdateRateLimit(id, request, info, lag, true); | |||
} | |||
await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false); | |||
continue; //Retry | |||
case HttpStatusCode.BadGateway: //502 | |||
Debug.WriteLine($"[{id}] (!) 502"); | |||
continue; //Continue | |||
default: | |||
string reason = null; | |||
if (response.Stream != null) | |||
{ | |||
try | |||
switch (response.StatusCode) | |||
{ | |||
case (HttpStatusCode)429: | |||
if (info.IsGlobal) | |||
{ | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] (!) 429 [Global]"); | |||
#endif | |||
_queue.PauseGlobal(info, lag); | |||
} | |||
else | |||
{ | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] (!) 429"); | |||
#endif | |||
UpdateRateLimit(id, request, info, lag, true); | |||
} | |||
await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false); | |||
continue; //Retry | |||
case HttpStatusCode.BadGateway: //502 | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] (!) 502"); | |||
#endif | |||
continue; //Continue | |||
default: | |||
string reason = null; | |||
if (response.Stream != null) | |||
{ | |||
using (var reader = new StreamReader(response.Stream)) | |||
using (var jsonReader = new JsonTextReader(reader)) | |||
try | |||
{ | |||
var json = JToken.Load(jsonReader); | |||
reason = json.Value<string>("message"); | |||
using (var reader = new StreamReader(response.Stream)) | |||
using (var jsonReader = new JsonTextReader(reader)) | |||
{ | |||
var json = JToken.Load(jsonReader); | |||
reason = json.Value<string>("message"); | |||
} | |||
} | |||
catch { } | |||
} | |||
catch { } | |||
} | |||
throw new HttpException(response.StatusCode, reason); | |||
throw new HttpException(response.StatusCode, reason); | |||
} | |||
} | |||
else | |||
{ | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Success"); | |||
#endif | |||
return response.Stream; | |||
} | |||
} | |||
else | |||
#if DEBUG_LIMITS | |||
catch | |||
{ | |||
Debug.WriteLine($"[{id}] Error"); | |||
throw; | |||
} | |||
#endif | |||
finally | |||
{ | |||
Debug.WriteLine($"[{id}] Success"); | |||
UpdateRateLimit(id, request, info, lag, false); | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Stop"); | |||
return response.Stream; | |||
#endif | |||
} | |||
} | |||
} | |||
@@ -135,7 +167,9 @@ namespace Discord.Net.Queue | |||
if (resetAt > timeoutAt) | |||
throw new RateLimitedException(); | |||
int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); | |||
#endif | |||
if (millis > 0) | |||
await Task.Delay(millis, request.CancelToken).ConfigureAwait(false); | |||
} | |||
@@ -143,13 +177,17 @@ namespace Discord.Net.Queue | |||
{ | |||
if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0) | |||
throw new RateLimitedException(); | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)"); | |||
#endif | |||
await Task.Delay(500, request.CancelToken).ConfigureAwait(false); | |||
} | |||
continue; | |||
} | |||
#if DEBUG_LIMITS | |||
else | |||
Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)"); | |||
#endif | |||
break; | |||
} | |||
} | |||
@@ -166,7 +204,9 @@ namespace Discord.Net.Queue | |||
{ | |||
WindowCount = info.Limit.Value; | |||
_semaphore = info.Remaining.Value; | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}"); | |||
#endif | |||
} | |||
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); | |||
@@ -182,24 +222,32 @@ namespace Discord.Net.Queue | |||
{ | |||
//RetryAfter is more accurate than Reset, where available | |||
resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value); | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); | |||
#endif | |||
} | |||
else if (info.Reset.HasValue) | |||
{ | |||
resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds); | |||
int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {lag.TotalMilliseconds} ms lag)"); | |||
#endif | |||
} | |||
else if (request.Options.ClientBucketId != null) | |||
else if (request.Options.IsClientBucket && request.Options.BucketId != null) | |||
{ | |||
resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds); | |||
Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds * 1000} ms)"); | |||
resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.BucketId).WindowSeconds); | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.BucketId).WindowSeconds * 1000} ms)"); | |||
#endif | |||
} | |||
if (resetTick == null) | |||
{ | |||
WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token) | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Disabled Semaphore"); | |||
#endif | |||
return; | |||
} | |||
@@ -207,7 +255,9 @@ namespace Discord.Net.Queue | |||
{ | |||
_resetTick = resetTick; | |||
LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms"); | |||
#endif | |||
if (!hasQueuedReset) | |||
{ | |||
@@ -227,7 +277,9 @@ namespace Discord.Net.Queue | |||
millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds); | |||
if (millis <= 0) //Make sure we havent gotten a more accurate reset time | |||
{ | |||
#if DEBUG_LIMITS | |||
Debug.WriteLine($"[{id}] * Reset *"); | |||
#endif | |||
_semaphore = WindowCount; | |||
_resetTick = null; | |||
return; | |||
@@ -236,4 +288,4 @@ namespace Discord.Net.Queue | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -120,7 +120,7 @@ namespace Discord.Net.Rest | |||
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token; | |||
HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); | |||
var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault()); | |||
var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); | |||
var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null; | |||
return new RestResponse(response.StatusCode, headers, stream); | |||
@@ -101,6 +101,8 @@ namespace Discord.Net.WebSockets | |||
if (_client != null && _client.State == WebSocketState.Open) | |||
{ | |||
var token = new CancellationToken(); | |||
await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", token); | |||
_client.Dispose(); | |||
_client = null; | |||
} | |||
@@ -10,7 +10,7 @@ | |||
internal bool IgnoreState { get; set; } | |||
internal string BucketId { get; set; } | |||
internal string ClientBucketId { get; set; } | |||
internal bool IsClientBucket { get; set; } | |||
internal static RequestOptions CreateOrClone(RequestOptions options) | |||
{ | |||
@@ -22,7 +22,7 @@ | |||
public RequestOptions() | |||
{ | |||
Timeout = 30000; | |||
Timeout = DiscordConfig.DefaultRequestTimeout; | |||
} | |||
public RequestOptions Clone() => MemberwiseClone() as RequestOptions; | |||
@@ -85,14 +85,17 @@ namespace Discord | |||
return false; | |||
} | |||
internal static string Resolve(IMessage msg, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling) | |||
internal static string Resolve(IMessage msg, int startIndex, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling) | |||
{ | |||
var text = new StringBuilder(msg.Content); | |||
var text = new StringBuilder(msg.Content.Substring(startIndex)); | |||
var tags = msg.Tags; | |||
int indexOffset = 0; | |||
int indexOffset = -startIndex; | |||
foreach (var tag in tags) | |||
{ | |||
if (tag.Index < startIndex) | |||
continue; | |||
string newText = ""; | |||
switch (tag.Type) | |||
{ | |||
@@ -139,12 +142,22 @@ namespace Discord | |||
if (user != null) | |||
return $"@{guildUser?.Nickname ?? user?.Username}"; | |||
else | |||
return $"@unknown-user"; | |||
return $""; | |||
case TagHandling.NameNoPrefix: | |||
if (user != null) | |||
return $"{guildUser?.Nickname ?? user?.Username}"; | |||
else | |||
return $""; | |||
case TagHandling.FullName: | |||
if (user != null) | |||
return $"@{user.Username}#{user.Discriminator}"; | |||
else | |||
return $"@unknown-user"; | |||
return $""; | |||
case TagHandling.FullNameNoPrefix: | |||
if (user != null) | |||
return $"{user.Username}#{user.Discriminator}"; | |||
else | |||
return $""; | |||
case TagHandling.Sanitize: | |||
if (guildUser != null && guildUser.Nickname == null) | |||
return MentionUser($"{SanitizeChar}{tag.Key}", false); | |||
@@ -166,7 +179,13 @@ namespace Discord | |||
if (channel != null) | |||
return $"#{channel.Name}"; | |||
else | |||
return $"#deleted-channel"; | |||
return $""; | |||
case TagHandling.NameNoPrefix: | |||
case TagHandling.FullNameNoPrefix: | |||
if (channel != null) | |||
return $"{channel.Name}"; | |||
else | |||
return $""; | |||
case TagHandling.Sanitize: | |||
return MentionChannel($"{SanitizeChar}{tag.Key}"); | |||
} | |||
@@ -185,7 +204,13 @@ namespace Discord | |||
if (role != null) | |||
return $"@{role.Name}"; | |||
else | |||
return $"@deleted-role"; | |||
return $""; | |||
case TagHandling.NameNoPrefix: | |||
case TagHandling.FullNameNoPrefix: | |||
if (role != null) | |||
return $"{role.Name}"; | |||
else | |||
return $""; | |||
case TagHandling.Sanitize: | |||
return MentionRole($"{SanitizeChar}{tag.Key}"); | |||
} | |||
@@ -200,7 +225,9 @@ namespace Discord | |||
{ | |||
case TagHandling.Name: | |||
case TagHandling.FullName: | |||
return "@everyone"; | |||
case TagHandling.NameNoPrefix: | |||
case TagHandling.FullNameNoPrefix: | |||
return "everyone"; | |||
case TagHandling.Sanitize: | |||
return $"@{SanitizeChar}everyone"; | |||
} | |||
@@ -215,9 +242,11 @@ namespace Discord | |||
{ | |||
case TagHandling.Name: | |||
case TagHandling.FullName: | |||
return "@everyone"; | |||
case TagHandling.NameNoPrefix: | |||
case TagHandling.FullNameNoPrefix: | |||
return "here"; | |||
case TagHandling.Sanitize: | |||
return $"@{SanitizeChar}everyone"; | |||
return $"@{SanitizeChar}here"; | |||
} | |||
} | |||
return ""; | |||
@@ -232,8 +261,11 @@ namespace Discord | |||
case TagHandling.Name: | |||
case TagHandling.FullName: | |||
return $":{emoji.Name}:"; | |||
case TagHandling.NameNoPrefix: | |||
case TagHandling.FullNameNoPrefix: | |||
return $"{emoji.Name}"; | |||
case TagHandling.Sanitize: | |||
return $"<@{SanitizeChar}everyone"; | |||
return $"<{emoji.Id}{SanitizeChar}:{SanitizeChar}{emoji.Name}>"; | |||
} | |||
} | |||
return ""; | |||
@@ -5,148 +5,182 @@ namespace Discord | |||
internal static class Preconditions | |||
{ | |||
//Objects | |||
public static void NotNull<T>(T obj, string name) where T : class { if (obj == null) throw new ArgumentNullException(name); } | |||
public static void NotNull<T>(Optional<T> obj, string name) where T : class { if (obj.IsSpecified && obj.Value == null) throw new ArgumentNullException(name); } | |||
public static void NotNull<T>(T obj, string name, string msg = null) where T : class { if (obj == null) throw CreateNotNullException(name, msg); } | |||
public static void NotNull<T>(Optional<T> obj, string name, string msg = null) where T : class { if (obj.IsSpecified && obj.Value == null) throw CreateNotNullException(name, msg); } | |||
private static ArgumentNullException CreateNotNullException(string name, string msg) | |||
{ | |||
if (msg == null) return new ArgumentNullException(name); | |||
else return new ArgumentNullException(name, msg); | |||
} | |||
//Strings | |||
public static void NotEmpty(string obj, string name) { if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } | |||
public static void NotEmpty(Optional<string> obj, string name) { if (obj.IsSpecified && obj.Value.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } | |||
public static void NotNullOrEmpty(string obj, string name) | |||
public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } | |||
public static void NotEmpty(Optional<string> obj, string name, string msg = null) { if (obj.IsSpecified && obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); } | |||
public static void NotNullOrEmpty(string obj, string name, string msg = null) | |||
{ | |||
if (obj == null) | |||
throw new ArgumentNullException(name); | |||
if (obj.Length == 0) | |||
throw new ArgumentException("Argument cannot be empty.", name); | |||
if (obj == null) throw CreateNotNullException(name, msg); | |||
if (obj.Length == 0) throw CreateNotEmptyException(name, msg); | |||
} | |||
public static void NotNullOrEmpty(Optional<string> obj, string name) | |||
public static void NotNullOrEmpty(Optional<string> obj, string name, string msg = null) | |||
{ | |||
if (obj.IsSpecified) | |||
{ | |||
if (obj.Value == null) | |||
throw new ArgumentNullException(name); | |||
if (obj.Value.Length == 0) | |||
throw new ArgumentException("Argument cannot be empty.", name); | |||
if (obj.Value == null) throw CreateNotNullException(name, msg); | |||
if (obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); | |||
} | |||
} | |||
public static void NotNullOrWhitespace(string obj, string name) | |||
public static void NotNullOrWhitespace(string obj, string name, string msg = null) | |||
{ | |||
if (obj == null) | |||
throw new ArgumentNullException(name); | |||
if (obj.Trim().Length == 0) | |||
throw new ArgumentException("Argument cannot be blank.", name); | |||
if (obj == null) throw CreateNotNullException(name, msg); | |||
if (obj.Trim().Length == 0) throw CreateNotEmptyException(name, msg); | |||
} | |||
public static void NotNullOrWhitespace(Optional<string> obj, string name) | |||
public static void NotNullOrWhitespace(Optional<string> obj, string name, string msg = null) | |||
{ | |||
if (obj.IsSpecified) | |||
{ | |||
if (obj.Value == null) | |||
throw new ArgumentNullException(name); | |||
if (obj.Value.Trim().Length == 0) | |||
throw new ArgumentException("Argument cannot be blank.", name); | |||
if (obj.Value == null) throw CreateNotNullException(name, msg); | |||
if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg); | |||
} | |||
} | |||
private static ArgumentException CreateNotEmptyException(string name, string msg) | |||
{ | |||
if (msg == null) return new ArgumentException(name, "Argument cannot be blank."); | |||
else return new ArgumentException(name, msg); | |||
} | |||
//Numerics | |||
public static void NotEqual(sbyte obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(byte obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(short obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(ushort obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(int obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(uint obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(long obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(ulong obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(sbyte? obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(byte? obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(short? obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(ushort? obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(int? obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(uint? obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(long? obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(ulong? obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<sbyte?> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<byte?> obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<short?> obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<ushort?> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<int?> obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<uint?> obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<long?> obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(Optional<ulong?> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(sbyte obj, sbyte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(byte obj, byte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(short obj, short value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(ushort obj, ushort value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(int obj, int value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(uint obj, uint value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(long obj, long value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(ulong obj, ulong value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtLeast(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(sbyte obj, sbyte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(byte obj, byte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(short obj, short value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(ushort obj, ushort value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(int obj, int value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(uint obj, uint value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(long obj, long value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(ulong obj, ulong value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void GreaterThan(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(sbyte obj, sbyte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(byte obj, byte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(short obj, short value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(ushort obj, ushort value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(int obj, int value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(uint obj, uint value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(long obj, long value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(ulong obj, ulong value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void AtMost(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(sbyte obj, sbyte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(byte obj, byte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(short obj, short value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(ushort obj, ushort value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(int obj, int value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(uint obj, uint value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(long obj, long value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(ulong obj, ulong value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void LessThan(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } | |||
public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(byte obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(short obj, short value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(ushort obj, ushort value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(int obj, int value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(uint obj, uint value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(long obj, long value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(ulong obj, ulong value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(sbyte? obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(byte? obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(short? obj, short value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(ushort? obj, ushort value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(int? obj, int value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(uint? obj, uint value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(long? obj, long value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(ulong? obj, ulong value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<sbyte?> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<byte?> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<short?> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<ushort?> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<int?> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<uint?> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<long?> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
public static void NotEqual(Optional<ulong?> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } | |||
private static ArgumentException CreateNotEqualException<T>(string name, string msg, T value) | |||
{ | |||
if (msg == null) return new ArgumentException($"Value may not be equal to {value}", name); | |||
else return new ArgumentException(msg, name); | |||
} | |||
public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(byte obj, byte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(short obj, short value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(ushort obj, ushort value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(int obj, int value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(uint obj, uint value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(long obj, long value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(ulong obj, ulong value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } | |||
public static void AtLeast(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } | |||
private static ArgumentException CreateAtLeastException<T>(string name, string msg, T value) | |||
{ | |||
if (msg == null) return new ArgumentException($"Value must be at least {value}", name); | |||
else return new ArgumentException(msg, name); | |||
} | |||
public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(byte obj, byte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(short obj, short value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(ushort obj, ushort value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(int obj, int value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(uint obj, uint value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(long obj, long value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(ulong obj, ulong value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } | |||
public static void GreaterThan(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } | |||
private static ArgumentException CreateGreaterThanException<T>(string name, string msg, T value) | |||
{ | |||
if (msg == null) return new ArgumentException($"Value must be greater than {value}", name); | |||
else return new ArgumentException(msg, name); | |||
} | |||
public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(byte obj, byte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(short obj, short value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(ushort obj, ushort value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(int obj, int value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(uint obj, uint value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(long obj, long value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(ulong obj, ulong value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } | |||
public static void AtMost(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } | |||
private static ArgumentException CreateAtMostException<T>(string name, string msg, T value) | |||
{ | |||
if (msg == null) return new ArgumentException($"Value must be at most {value}", name); | |||
else return new ArgumentException(msg, name); | |||
} | |||
public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(byte obj, byte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(short obj, short value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(ushort obj, ushort value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(int obj, int value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(uint obj, uint value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(long obj, long value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(ulong obj, ulong value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } | |||
public static void LessThan(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } | |||
private static ArgumentException CreateLessThanException<T>(string name, string msg, T value) | |||
{ | |||
if (msg == null) return new ArgumentException($"Value must be less than {value}", name); | |||
else return new ArgumentException(msg, name); | |||
} | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
{ | |||
"version": "1.0.0-beta2-*", | |||
{ | |||
"version": "1.0.0-*", | |||
"description": "A .Net API wrapper and bot framework for Discord.", | |||
"authors": [ "RogueException" ], | |||
@@ -26,14 +26,14 @@ | |||
}, | |||
"dependencies": { | |||
"Microsoft.Win32.Primitives": "4.0.1", | |||
"Microsoft.Win32.Primitives": "4.3.0", | |||
"Newtonsoft.Json": "9.0.1", | |||
"System.Collections.Concurrent": "4.0.12", | |||
"System.Collections.Immutable": "1.2.0", | |||
"System.Interactive.Async": "3.0.0", | |||
"System.Net.Http": "4.1.0", | |||
"System.Collections.Concurrent": "4.3.0", | |||
"System.Collections.Immutable": "1.3.0", | |||
"System.Interactive.Async": "3.1.0", | |||
"System.Net.Http": "4.3.0", | |||
"System.Net.WebSockets.Client": { | |||
"version": "4.0.0", | |||
"version": "4.3.0", | |||
"type": "build" | |||
} | |||
}, | |||
@@ -0,0 +1,44 @@ | |||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" /> | |||
<PropertyGroup> | |||
<Description>A core Discord.Net library containing the REST client and models.</Description> | |||
<VersionPrefix>1.0.0-beta2</VersionPrefix> | |||
<TargetFramework>netstandard1.3</TargetFramework> | |||
<AssemblyName>Discord.Net.Rest</AssemblyName> | |||
<PackageTags>discord;discordapp</PackageTags> | |||
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | |||
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | |||
<RepositoryType>git</RepositoryType> | |||
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl> | |||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Compile Include="**\*.cs" /> | |||
<EmbeddedResource Include="**\*.resx" /> | |||
<EmbeddedResource Include="compiler\resources\**\*" /> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.NET.Sdk"> | |||
<Version>1.0.0-alpha-20161104-2</Version> | |||
<PrivateAssets>All</PrivateAssets> | |||
</PackageReference> | |||
<PackageReference Include="System.IO.FileSystem"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<PropertyGroup Label="Configuration"> | |||
<SignAssembly>False</SignAssembly> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
</Project> |
@@ -1,19 +0,0 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||
<PropertyGroup Label="Globals"> | |||
<ProjectGuid>bfc6dc28-0351-4573-926a-d4124244c04f</ProjectGuid> | |||
<RootNamespace>Discord.Rest</RootNamespace> | |||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<SchemaVersion>2.0</SchemaVersion> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||
</Project> |
@@ -8,6 +8,8 @@ namespace Discord.Rest | |||
{ | |||
public class DiscordRestClient : BaseDiscordClient, IDiscordClient | |||
{ | |||
private RestApplication _applicationInfo; | |||
public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | |||
public DiscordRestClient() : this(new DiscordRestConfig()) { } | |||
@@ -21,10 +23,17 @@ namespace Discord.Rest | |||
base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); | |||
return Task.CompletedTask; | |||
} | |||
protected override Task OnLogoutAsync() | |||
{ | |||
_applicationInfo = null; | |||
return Task.CompletedTask; | |||
} | |||
/// <inheritdoc /> | |||
public Task<RestApplication> GetApplicationInfoAsync() | |||
=> ClientHelper.GetApplicationInfoAsync(this); | |||
public async Task<RestApplication> GetApplicationInfoAsync() | |||
{ | |||
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this)); | |||
} | |||
/// <inheritdoc /> | |||
public Task<RestChannel> GetChannelAsync(ulong id) | |||
@@ -6,7 +6,6 @@ namespace Discord.Rest | |||
{ | |||
public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; | |||
internal const int RestTimeout = 10000; | |||
internal const int MessageQueueInterval = 100; | |||
internal const int WebSocketQueueInterval = 100; | |||
@@ -6,6 +6,7 @@ using System.IO; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Channel; | |||
using UserModel = Discord.API.User; | |||
namespace Discord.Rest | |||
{ | |||
@@ -43,13 +44,13 @@ namespace Discord.Rest | |||
} | |||
//Invites | |||
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IChannel channel, BaseDiscordClient client, | |||
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client, | |||
RequestOptions options) | |||
{ | |||
var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options).ConfigureAwait(false); | |||
return models.Select(x => RestInviteMetadata.Create(client, null, channel, x)).ToImmutableArray(); | |||
} | |||
public static async Task<RestInviteMetadata> CreateInviteAsync(IChannel channel, BaseDiscordClient client, | |||
public static async Task<RestInviteMetadata> CreateInviteAsync(IGuildChannel channel, BaseDiscordClient client, | |||
int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) | |||
{ | |||
var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; | |||
@@ -62,18 +63,24 @@ namespace Discord.Rest | |||
} | |||
//Messages | |||
public static async Task<RestMessage> GetMessageAsync(IChannel channel, BaseDiscordClient client, | |||
ulong id, IGuild guild, RequestOptions options) | |||
public static async Task<RestMessage> GetMessageAsync(IMessageChannel channel, BaseDiscordClient client, | |||
ulong id, RequestOptions options) | |||
{ | |||
var guildId = (channel as IGuildChannel)?.GuildId; | |||
var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; | |||
var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); | |||
return RestMessage.Create(client, guild, model); | |||
var author = GetAuthor(client, guild, model.Author.Value); | |||
return RestMessage.Create(client, channel, author, model); | |||
} | |||
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IChannel channel, BaseDiscordClient client, | |||
ulong? fromMessageId, Direction dir, int limit, IGuild guild, RequestOptions options) | |||
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, | |||
ulong? fromMessageId, Direction dir, int limit, RequestOptions options) | |||
{ | |||
if (dir == Direction.Around) | |||
throw new NotImplementedException(); //TODO: Impl | |||
var guildId = (channel as IGuildChannel)?.GuildId; | |||
var guild = guildId != null ? (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; | |||
return new PagedAsyncEnumerable<RestMessage>( | |||
DiscordConfig.MaxMessagesPerBatch, | |||
async (info, ct) => | |||
@@ -85,8 +92,15 @@ namespace Discord.Rest | |||
}; | |||
if (info.Position != null) | |||
args.RelativeMessageId = info.Position.Value; | |||
var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options).ConfigureAwait(false); | |||
return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray(); | |||
var builder = ImmutableArray.CreateBuilder<RestMessage>(); | |||
foreach (var model in models) | |||
{ | |||
var author = GetAuthor(client, guild, model.Author.Value); | |||
builder.Add(RestMessage.Create(client, channel, author, model)); | |||
} | |||
return builder.ToImmutable(); | |||
}, | |||
nextPage: (info, lastPage) => | |||
{ | |||
@@ -102,37 +116,45 @@ namespace Discord.Rest | |||
count: limit | |||
); | |||
} | |||
public static async Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client, | |||
IGuild guild, RequestOptions options) | |||
public static async Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(IMessageChannel channel, BaseDiscordClient client, | |||
RequestOptions options) | |||
{ | |||
var guildId = (channel as IGuildChannel)?.GuildId; | |||
var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; | |||
var models = await client.ApiClient.GetPinsAsync(channel.Id, options).ConfigureAwait(false); | |||
return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray(); | |||
var builder = ImmutableArray.CreateBuilder<RestMessage>(); | |||
foreach (var model in models) | |||
{ | |||
var author = GetAuthor(client, guild, model.Author.Value); | |||
builder.Add(RestMessage.Create(client, channel, author, model)); | |||
} | |||
return builder.ToImmutable(); | |||
} | |||
public static async Task<RestUserMessage> SendMessageAsync(IChannel channel, BaseDiscordClient client, | |||
string text, bool isTTS, IGuild guild, RequestOptions options) | |||
public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, | |||
string text, bool isTTS, EmbedBuilder embed, RequestOptions options) | |||
{ | |||
var args = new CreateMessageParams(text) { IsTTS = isTTS }; | |||
var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.Build() }; | |||
var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | |||
return RestUserMessage.Create(client, guild, model); | |||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||
} | |||
public static async Task<RestUserMessage> SendFileAsync(IChannel channel, BaseDiscordClient client, | |||
string filePath, string text, bool isTTS, IGuild guild, RequestOptions options) | |||
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | |||
string filePath, string text, bool isTTS, RequestOptions options) | |||
{ | |||
string filename = Path.GetFileName(filePath); | |||
using (var file = File.OpenRead(filePath)) | |||
return await SendFileAsync(channel, client, file, filename, text, isTTS, guild, options).ConfigureAwait(false); | |||
return await SendFileAsync(channel, client, file, filename, text, isTTS, options).ConfigureAwait(false); | |||
} | |||
public static async Task<RestUserMessage> SendFileAsync(IChannel channel, BaseDiscordClient client, | |||
Stream stream, string filename, string text, bool isTTS, IGuild guild, RequestOptions options) | |||
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | |||
Stream stream, string filename, string text, bool isTTS, RequestOptions options) | |||
{ | |||
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | |||
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | |||
return RestUserMessage.Create(client, guild, model); | |||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||
} | |||
public static async Task DeleteMessagesAsync(IChannel channel, BaseDiscordClient client, | |||
public static async Task DeleteMessagesAsync(IMessageChannel channel, BaseDiscordClient client, | |||
IEnumerable<IMessage> messages, RequestOptions options) | |||
{ | |||
var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); | |||
@@ -216,5 +238,16 @@ namespace Discord.Rest | |||
public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, | |||
RequestOptions options) | |||
=> new TypingNotifier(client, channel, options); | |||
//Helpers | |||
private static IUser GetAuthor(BaseDiscordClient client, IGuild guild, UserModel model) | |||
{ | |||
IUser author = null; | |||
if (guild != null) | |||
author = guild.GetUserAsync(model.Id, CacheMode.CacheOnly).Result; | |||
if (author == null) | |||
author = RestUser.Create(client, model); | |||
return author; | |||
} | |||
} | |||
} |
@@ -7,7 +7,7 @@ namespace Discord.Rest | |||
public interface IRestMessageChannel : IMessageChannel | |||
{ | |||
/// <summary> Sends a message to this message channel. </summary> | |||
new Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null); | |||
new Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null); | |||
/// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); | |||
/// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||
@@ -53,22 +53,22 @@ namespace Discord.Rest | |||
} | |||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options); | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); | |||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | |||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | |||
@@ -126,8 +126,8 @@ namespace Discord.Rest | |||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | |||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
=> EnterTypingState(options); | |||
@@ -66,22 +66,22 @@ namespace Discord.Rest | |||
} | |||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options); | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); | |||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | |||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | |||
@@ -136,8 +136,8 @@ namespace Discord.Rest | |||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | |||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
=> EnterTypingState(options); | |||
@@ -45,22 +45,22 @@ namespace Discord.Rest | |||
=> ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); | |||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options); | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); | |||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | |||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | |||
@@ -108,8 +108,8 @@ namespace Discord.Rest | |||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | |||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
=> EnterTypingState(options); | |||
@@ -23,22 +23,22 @@ namespace Discord.Rest | |||
} | |||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options); | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS, EmbedBuilder embed = null, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); | |||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | |||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | |||
@@ -86,8 +86,8 @@ namespace Discord.Rest | |||
=> await SendFileAsync(filePath, text, isTTS, options); | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | |||
=> await SendFileAsync(stream, filename, text, isTTS, options); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, options); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, embed, options); | |||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
=> EnterTypingState(options); | |||
@@ -142,7 +142,7 @@ namespace Discord.Rest | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false); | |||
var role = RestRole.Create(client, model); | |||
var role = RestRole.Create(client, guild, model); | |||
await role.ModifyAsync(x => | |||
{ | |||
@@ -87,7 +87,7 @@ namespace Discord.Rest | |||
if (model.Roles != null) | |||
{ | |||
for (int i = 0; i < model.Roles.Length; i++) | |||
roles[model.Roles[i].Id] = RestRole.Create(Discord, model.Roles[i]); | |||
roles[model.Roles[i].Id] = RestRole.Create(Discord, this, model.Roles[i]); | |||
} | |||
_roles = roles.ToImmutable(); | |||
@@ -1,4 +1,7 @@ | |||
using System.Diagnostics; | |||
using System; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using Model = Discord.API.Embed; | |||
namespace Discord | |||
@@ -10,23 +13,55 @@ namespace Discord | |||
public string Url { get; } | |||
public string Title { get; } | |||
public string Type { get; } | |||
public DateTimeOffset? Timestamp { get; } | |||
public Color? Color { get; } | |||
public EmbedImage? Image { get; } | |||
public EmbedVideo? Video { get; } | |||
public EmbedAuthor? Author { get; } | |||
public EmbedFooter? Footer { get; } | |||
public EmbedProvider? Provider { get; } | |||
public EmbedThumbnail? Thumbnail { get; } | |||
public ImmutableArray<EmbedField> Fields { get; } | |||
internal Embed(string type, string title, string description, string url, EmbedProvider? provider, EmbedThumbnail? thumbnail) | |||
internal Embed(string type, | |||
string title, | |||
string description, | |||
string url, | |||
DateTimeOffset? timestamp, | |||
Color? color, | |||
EmbedImage? image, | |||
EmbedVideo? video, | |||
EmbedAuthor? author, | |||
EmbedFooter? footer, | |||
EmbedProvider? provider, | |||
EmbedThumbnail? thumbnail, | |||
ImmutableArray<EmbedField> fields) | |||
{ | |||
Type = type; | |||
Title = title; | |||
Description = description; | |||
Url = url; | |||
Color = color; | |||
Timestamp = timestamp; | |||
Image = image; | |||
Video = video; | |||
Author = author; | |||
Footer = footer; | |||
Provider = provider; | |||
Thumbnail = thumbnail; | |||
Fields = fields; | |||
} | |||
internal static Embed Create(Model model) | |||
{ | |||
return new Embed(model.Type, model.Title, model.Description, model.Url, | |||
return new Embed(model.Type, model.Title, model.Description, model.Url,model.Timestamp, | |||
model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, | |||
model.Image.IsSpecified ? EmbedImage.Create(model.Image.Value) : (EmbedImage?)null, | |||
model.Video.IsSpecified ? EmbedVideo.Create(model.Video.Value) : (EmbedVideo?)null, | |||
model.Author.IsSpecified ? EmbedAuthor.Create(model.Author.Value) : (EmbedAuthor?)null, | |||
model.Footer.IsSpecified ? EmbedFooter.Create(model.Footer.Value) : (EmbedFooter?)null, | |||
model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, | |||
model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null); | |||
model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null, | |||
model.Fields.IsSpecified ? model.Fields.Value.Select(EmbedField.Create).ToImmutableArray() : ImmutableArray.Create<EmbedField>()); | |||
} | |||
public override string ToString() => Title; | |||
@@ -15,7 +15,12 @@ namespace Discord.Rest | |||
{ | |||
var args = new ModifyMessageParams(); | |||
func(args); | |||
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, args, options).ConfigureAwait(false); | |||
var apiArgs = new API.Rest.ModifyMessageParams | |||
{ | |||
Content = args.Content, | |||
Embed = args.Embed.IsSpecified ? args.Embed.Value.Build() : Optional.Create<API.Embed>() | |||
}; | |||
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | |||
} | |||
public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, | |||
RequestOptions options) | |||
@@ -9,11 +9,10 @@ namespace Discord.Rest | |||
{ | |||
public abstract class RestMessage : RestEntity<ulong>, IMessage, IUpdateable | |||
{ | |||
internal readonly IGuild _guild; | |||
private long _timestampTicks; | |||
public IMessageChannel Channel { get; } | |||
public RestUser Author { get; } | |||
public IUser Author { get; } | |||
public string Content { get; private set; } | |||
@@ -32,19 +31,18 @@ namespace Discord.Rest | |||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | |||
internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) | |||
internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
: base(discord, id) | |||
{ | |||
Channel = channel; | |||
Author = author; | |||
_guild = guild; | |||
} | |||
internal static RestMessage Create(BaseDiscordClient discord, IGuild guild, Model model) | |||
internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
{ | |||
if (model.Type == MessageType.Default) | |||
return RestUserMessage.Create(discord, guild, model); | |||
return RestUserMessage.Create(discord, channel, author, model); | |||
else | |||
return RestSystemMessage.Create(discord, guild, model); | |||
return RestSystemMessage.Create(discord, channel, author, model); | |||
} | |||
internal virtual void Update(Model model) | |||
{ | |||
@@ -8,15 +8,13 @@ namespace Discord.Rest | |||
{ | |||
public MessageType Type { get; private set; } | |||
internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) | |||
: base(discord, id, channel, author, guild) | |||
internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
: base(discord, id, channel, author) | |||
{ | |||
} | |||
internal new static RestSystemMessage Create(BaseDiscordClient discord, IGuild guild, Model model) | |||
internal new static RestSystemMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
{ | |||
var entity = new RestSystemMessage(discord, model.Id, | |||
RestVirtualMessageChannel.Create(discord, model.ChannelId), | |||
RestUser.Create(discord, model.Author.Value), guild); | |||
var entity = new RestSystemMessage(discord, model.Id, channel, author); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
@@ -3,7 +3,6 @@ using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Message; | |||
@@ -32,15 +31,13 @@ namespace Discord.Rest | |||
public override IReadOnlyCollection<ITag> Tags => _tags; | |||
public IReadOnlyDictionary<Emoji, int> Reactions => _reactions.ToDictionary(x => x.Emoji, x => x.Count); | |||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) | |||
: base(discord, id, channel, author, guild) | |||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
: base(discord, id, channel, author) | |||
{ | |||
} | |||
internal new static RestUserMessage Create(BaseDiscordClient discord, IGuild guild, Model model) | |||
internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
{ | |||
var entity = new RestUserMessage(discord, model.Id, | |||
RestVirtualMessageChannel.Create(discord, model.ChannelId), | |||
RestUser.Create(discord, model.Author.Value), guild); | |||
var entity = new RestUserMessage(discord, model.Id, channel, author); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
@@ -122,7 +119,9 @@ namespace Discord.Rest | |||
if (model.Content.IsSpecified) | |||
{ | |||
var text = model.Content.Value; | |||
_tags = MessageHelper.ParseTags(text, null, _guild, mentions); | |||
var guildId = (Channel as IGuildChannel)?.GuildId; | |||
var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; | |||
_tags = MessageHelper.ParseTags(text, null, guild, mentions); | |||
model.Content = text; | |||
} | |||
} | |||
@@ -155,9 +154,12 @@ namespace Discord.Rest | |||
public Task UnpinAsync(RequestOptions options) | |||
=> MessageHelper.UnpinAsync(this, Discord, options); | |||
public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
=> MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
=> MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; | |||
} | |||
@@ -9,7 +9,7 @@ namespace Discord.Rest | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestRole : RestEntity<ulong>, IRole | |||
{ | |||
public RestGuild Guild { get; } | |||
internal IGuild Guild { get; } | |||
public Color Color { get; private set; } | |||
public bool IsHoisted { get; private set; } | |||
public bool IsManaged { get; private set; } | |||
@@ -22,13 +22,14 @@ namespace Discord.Rest | |||
public bool IsEveryone => Id == Guild.Id; | |||
public string Mention => MentionUtils.MentionRole(Id); | |||
internal RestRole(BaseDiscordClient discord, ulong id) | |||
internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) | |||
: base(discord, id) | |||
{ | |||
Guild = guild; | |||
} | |||
internal static RestRole Create(BaseDiscordClient discord, Model model) | |||
internal static RestRole Create(BaseDiscordClient discord, IGuild guild, Model model) | |||
{ | |||
var entity = new RestRole(discord, model.Id); | |||
var entity = new RestRole(discord, guild, model.Id); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
@@ -51,10 +52,20 @@ namespace Discord.Rest | |||
public Task DeleteAsync(RequestOptions options = null) | |||
=> RoleHelper.DeleteAsync(this, Discord, options); | |||
public int CompareTo(IRole role) => this.Compare(role); | |||
public override string ToString() => Name; | |||
private string DebuggerDisplay => $"{Name} ({Id})"; | |||
//IRole | |||
IGuild IRole.Guild => Guild; | |||
IGuild IRole.Guild | |||
{ | |||
get | |||
{ | |||
if (Guild != null) | |||
return Guild; | |||
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); | |||
} | |||
} | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
{ | |||
"version": "1.0.0-beta2-*", | |||
{ | |||
"version": "1.0.0-*", | |||
"description": "A core Discord.Net library containing the REST client and models.", | |||
"authors": [ "RogueException" ], | |||
@@ -29,7 +29,7 @@ | |||
"Discord.Net.Core": { | |||
"target": "project" | |||
}, | |||
"System.IO.FileSystem": "4.0.1" | |||
"System.IO.FileSystem": "4.3.0" | |||
}, | |||
"frameworks": { | |||
@@ -41,4 +41,4 @@ | |||
] | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,48 @@ | |||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" /> | |||
<PropertyGroup> | |||
<Description>A core Discord.Net library containing the RPC client and models.</Description> | |||
<VersionPrefix>1.0.0-beta2</VersionPrefix> | |||
<TargetFramework>netstandard1.3</TargetFramework> | |||
<AssemblyName>Discord.Net.Rpc</AssemblyName> | |||
<PackageTags>discord;discordapp</PackageTags> | |||
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | |||
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | |||
<RepositoryType>git</RepositoryType> | |||
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl> | |||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Compile Include="**\*.cs" /> | |||
<EmbeddedResource Include="**\*.resx" /> | |||
<EmbeddedResource Include="compiler\resources\**\*" /> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.NET.Sdk"> | |||
<Version>1.0.0-alpha-20161104-2</Version> | |||
<PrivateAssets>All</PrivateAssets> | |||
</PackageReference> | |||
<PackageReference Include="System.IO.Compression"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Net.WebSockets.Client"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<PropertyGroup Label="Configuration"> | |||
<SignAssembly>False</SignAssembly> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
</Project> |
@@ -1,19 +0,0 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||
<PropertyGroup Label="Globals"> | |||
<ProjectGuid>5688a353-121e-40a1-8bfa-b17b91fb48fb</ProjectGuid> | |||
<RootNamespace>Discord.Rpc</RootNamespace> | |||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<SchemaVersion>2.0</SchemaVersion> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||
</Project> |
@@ -14,7 +14,7 @@ using System.Threading.Tasks; | |||
namespace Discord.Rpc | |||
{ | |||
public partial class DiscordRpcClient : BaseDiscordClient | |||
public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient | |||
{ | |||
private readonly Logger _rpcLogger; | |||
private readonly JsonSerializer _serializer; | |||
@@ -33,7 +33,7 @@ namespace Discord.Rpc | |||
public new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient; | |||
public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } } | |||
public RestApplication CurrentApplication { get; private set; } | |||
public RestApplication ApplicationInfo { get; private set; } | |||
/// <summary> Creates a new RPC discord client. </summary> | |||
public DiscordRpcClient(string clientId, string origin) | |||
@@ -393,7 +393,7 @@ namespace Discord.Rpc | |||
{ | |||
var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); | |||
CurrentUser = RestSelfUser.Create(this, response.User); | |||
CurrentApplication = RestApplication.Create(this, response.Application); | |||
ApplicationInfo = RestApplication.Create(this, response.Application); | |||
Scopes = response.Scopes; | |||
TokenExpiresAt = response.Expires; | |||
@@ -547,5 +547,8 @@ namespace Discord.Rpc | |||
return; | |||
} | |||
} | |||
//IDiscordClient | |||
Task<IApplication> IDiscordClient.GetApplicationInfoAsync() => Task.FromResult<IApplication>(ApplicationInfo); | |||
} | |||
} |
@@ -34,22 +34,22 @@ namespace Discord.Rpc | |||
//TODO: Use RPC cache | |||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options); | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); | |||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | |||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | |||
@@ -104,8 +104,8 @@ namespace Discord.Rpc | |||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | |||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
=> EnterTypingState(options); | |||
@@ -36,22 +36,22 @@ namespace Discord.Rpc | |||
//TODO: Use RPC cache | |||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options); | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); | |||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | |||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | |||
@@ -103,8 +103,8 @@ namespace Discord.Rpc | |||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | |||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
=> EnterTypingState(options); | |||
@@ -39,22 +39,22 @@ namespace Discord.Rpc | |||
//TODO: Use RPC cache | |||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options); | |||
=> ChannelHelper.GetMessageAsync(this, Discord, id, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); | |||
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) | |||
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); | |||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); | |||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); | |||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | |||
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | |||
@@ -105,8 +105,8 @@ namespace Discord.Rpc | |||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | |||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); | |||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) | |||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | |||
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | |||
=> EnterTypingState(options); | |||
} | |||
@@ -123,9 +123,12 @@ namespace Discord.Rpc | |||
public Task UnpinAsync(RequestOptions options) | |||
=> MessageHelper.UnpinAsync(this, Discord, options); | |||
public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
=> MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
=> MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); | |||
private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; | |||
} | |||
@@ -1,5 +1,5 @@ | |||
{ | |||
"version": "1.0.0-beta2-*", | |||
"version": "1.0.0-*", | |||
"description": "A core Discord.Net library containing the RPC client and models.", | |||
"authors": [ "RogueException" ], | |||
@@ -32,8 +32,8 @@ | |||
"Discord.Net.Rest": { | |||
"target": "project" | |||
}, | |||
"System.IO.Compression": "4.1.0", | |||
"System.Net.WebSockets.Client": "4.0.0" | |||
"System.IO.Compression": "4.3.0", | |||
"System.Net.WebSockets.Client": "4.3.0" | |||
}, | |||
"frameworks": { | |||
@@ -45,4 +45,4 @@ | |||
] | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,58 @@ | |||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" /> | |||
<PropertyGroup> | |||
<Description>A core Discord.Net library containing the WebSocket client and models.</Description> | |||
<VersionPrefix>1.0.0-beta2</VersionPrefix> | |||
<TargetFramework>netstandard1.3</TargetFramework> | |||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
<AssemblyName>Discord.Net.WebSocket</AssemblyName> | |||
<PackageTags>discord;discordapp</PackageTags> | |||
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | |||
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | |||
<RepositoryType>git</RepositoryType> | |||
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl> | |||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Compile Include="**\*.cs" /> | |||
<EmbeddedResource Include="**\*.resx" /> | |||
<EmbeddedResource Include="compiler\resources\**\*" /> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.NET.Sdk"> | |||
<Version>1.0.0-alpha-20161104-2</Version> | |||
<PrivateAssets>All</PrivateAssets> | |||
</PackageReference> | |||
<PackageReference Include="System.IO.Compression"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Net.NameResolution"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Net.Sockets"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Net.WebSockets.Client"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
<PackageReference Include="System.Runtime.InteropServices"> | |||
<Version>4.3.0</Version> | |||
</PackageReference> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<PropertyGroup Label="Configuration"> | |||
<SignAssembly>False</SignAssembly> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
</Project> |
@@ -1,19 +0,0 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||
<PropertyGroup Label="Globals"> | |||
<ProjectGuid>22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d</ProjectGuid> | |||
<RootNamespace>Discord.WebSocket</RootNamespace> | |||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<SchemaVersion>2.0</SchemaVersion> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||
</Project> |
@@ -38,6 +38,7 @@ namespace Discord.WebSocket | |||
private int _nextAudioId; | |||
private bool _canReconnect; | |||
private DateTimeOffset? _statusSince; | |||
private RestApplication _applicationInfo; | |||
/// <summary> Gets the shard of of this client. </summary> | |||
public int ShardId { get; } | |||
@@ -123,6 +124,7 @@ namespace Discord.WebSocket | |||
if (ConnectionState != ConnectionState.Disconnected) | |||
await DisconnectInternalAsync(null, false).ConfigureAwait(false); | |||
_applicationInfo = null; | |||
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||
} | |||
@@ -333,8 +335,10 @@ namespace Discord.WebSocket | |||
} | |||
/// <inheritdoc /> | |||
public Task<RestApplication> GetApplicationInfoAsync() | |||
=> ClientHelper.GetApplicationInfoAsync(this); | |||
public async Task<RestApplication> GetApplicationInfoAsync() | |||
{ | |||
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this)); | |||
} | |||
/// <inheritdoc /> | |||
public SocketGuild GetGuild(ulong id) | |||