@@ -1,7 +1,7 @@ | |||||
| | ||||
Microsoft Visual Studio Solution File, Format Version 12.00 | 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 | MinimumVisualStudioVersion = 10.0.40219.1 | ||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}" | ||||
ProjectSection(SolutionItems) = preProject | ProjectSection(SolutionItems) = preProject | ||||
@@ -9,57 +9,109 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution | |||||
README.md = README.md | README.md = README.md | ||||
EndProjectSection | EndProjectSection | ||||
EndProject | 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 | 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 | EndProject | ||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" | ||||
EndProject | 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 | 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 | 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 | 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 | EndProject | ||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
Debug|x64 = Debug|x64 | |||||
Debug|x86 = Debug|x86 | |||||
Release|Any CPU = Release|Any CPU | Release|Any CPU = Release|Any CPU | ||||
Release|x64 = Release|x64 | |||||
Release|x86 = Release|x86 | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | {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}.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.ActiveCfg = Debug|Any CPU | ||||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU | ||||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU | ||||
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU | ||||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = 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 | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(NestedProjects) = preSolution | GlobalSection(NestedProjects) = preSolution | ||||
{BFC6DC28-0351-4573-926A-D4124244C04F} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | {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} | {5688A353-121E-40A1-8BFA-B17B91FB48FB} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | ||||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||||
EndGlobalSection | EndGlobalSection | ||||
EndGlobal | 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/feed/Packages/discord-net) | ||||
[](https://www.myget.org/) | [](https://www.myget.org/) | ||||
[](https://discord.gg/0SBTUU1wZTYLhAAW) | [](https://discord.gg/0SBTUU1wZTYLhAAW) | ||||
@@ -19,20 +19,15 @@ Bleeding edge builds are available using our MyGet feed (`https://www.myget.org/ | |||||
## Compiling | ## Compiling | ||||
In order to compile Discord.Net, you require the following: | 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 | @echo Off | ||||
dotnet restore | 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 | #### 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 | #### 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() | 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 class CommandAttribute : Attribute | ||||
{ | { | ||||
public string Text { get; } | public string Text { get; } | ||||
public RunMode RunMode { get; set; } = RunMode.Sync; | |||||
public RunMode RunMode { get; set; } = RunMode.Default; | |||||
public CommandAttribute() | 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 | Group = 0x04 | ||||
} | } | ||||
/// <summary> | |||||
/// Require that the command be invoked in a specified context. | |||||
/// </summary> | |||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | ||||
public class RequireContextAttribute : PreconditionAttribute | public class RequireContextAttribute : PreconditionAttribute | ||||
{ | { | ||||
public ContextType Contexts { get; } | 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) | public RequireContextAttribute(ContextType contexts) | ||||
{ | { | ||||
Contexts = 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 | 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)] | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] | ||||
public class RequirePermissionAttribute : PreconditionAttribute | |||||
public class RequireUserPermissionAttribute : PreconditionAttribute | |||||
{ | { | ||||
public GuildPermission? GuildPermission { get; } | public GuildPermission? GuildPermission { get; } | ||||
public ChannelPermission? ChannelPermission { 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; | GuildPermission = permission; | ||||
ChannelPermission = null; | 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; | ChannelPermission = permission; | ||||
GuildPermission = null; | 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) | 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); | StringBuilder argBuilder = new StringBuilder(input.Length); | ||||
int endPos = input.Length; | int endPos = input.Length; | ||||
var curPart = ParserPart.None; | 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."); | return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments."); | ||||
else | 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) | if (curParam != null && curParam.IsRemainder) | ||||
{ | { | ||||
argBuilder.Append(c); | argBuilder.Append(c); | ||||
@@ -116,11 +118,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
paramList.Add(typeReaderResult); | paramList.Add(typeReaderResult); | ||||
if (curPos == endPos) | |||||
{ | |||||
curParam = null; | |||||
curPart = ParserPart.None; | |||||
} | |||||
curPart = ParserPart.None; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -7,24 +7,30 @@ using System.Reflection; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Discord.Commands.Builders; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
public class CommandService | public class CommandService | ||||
{ | { | ||||
private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo(); | |||||
private readonly SemaphoreSlim _moduleLock; | private readonly SemaphoreSlim _moduleLock; | ||||
private readonly ConcurrentDictionary<Type, ModuleInfo> _moduleDefs; | |||||
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | |||||
private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; | private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; | ||||
private readonly ConcurrentBag<ModuleInfo> _moduleDefs; | |||||
private readonly CommandMap _map; | 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); | _moduleLock = new SemaphoreSlim(1, 1); | ||||
_moduleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | |||||
_typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | |||||
_moduleDefs = new ConcurrentBag<ModuleInfo>(); | |||||
_map = new CommandMap(); | _map = new CommandMap(); | ||||
_typeReaders = new ConcurrentDictionary<Type, TypeReader> | _typeReaders = new ConcurrentDictionary<Type, TypeReader> | ||||
{ | { | ||||
@@ -62,103 +68,129 @@ namespace Discord.Commands | |||||
[typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(), | [typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(), | ||||
[typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(), | [typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(), | ||||
}; | }; | ||||
_caseSensitive = config.CaseSensitiveCommands; | |||||
_defaultRunMode = config.DefaultRunMode; | |||||
} | } | ||||
//Modules | //Modules | ||||
public async Task<ModuleInfo> AddModule<T>() | |||||
public async Task<ModuleInfo> CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc) | |||||
{ | { | ||||
await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
try | 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."); | 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 | finally | ||||
{ | { | ||||
_moduleLock.Release(); | _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); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
try | 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 | finally | ||||
{ | { | ||||
_moduleLock.Release(); | _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); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
return RemoveModuleInternal(module.Source.BaseType); | |||||
return RemoveModuleInternal(module); | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_moduleLock.Release(); | _moduleLock.Release(); | ||||
} | } | ||||
} | } | ||||
public async Task<bool> RemoveModule<T>() | |||||
public async Task<bool> RemoveModuleAsync<T>() | |||||
{ | { | ||||
await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
return RemoveModuleInternal(typeof(T)); | |||||
ModuleInfo module; | |||||
_typedModuleDefs.TryGetValue(typeof(T), out module); | |||||
if (module == default(ModuleInfo)) | |||||
return false; | |||||
return RemoveModuleInternal(module); | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_moduleLock.Release(); | _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 | //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, int argPos) => Search(context, context.Message.Content.Substring(argPos)); | ||||
public SearchResult Search(CommandContext context, string input) | 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(); | var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray(); | ||||
if (matches.Length > 0) | if (matches.Length > 0) | ||||
@@ -191,9 +223,9 @@ namespace Discord.Commands | |||||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | 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; | dependencyMap = dependencyMap ?? DependencyMap.Empty; | ||||
@@ -204,7 +236,7 @@ namespace Discord.Commands | |||||
var commands = searchResult.Commands; | var commands = searchResult.Commands; | ||||
for (int i = commands.Count - 1; i >= 0; i--) | 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 (!preconditionResult.IsSuccess) | ||||
{ | { | ||||
if (commands.Count == 1) | if (commands.Count == 1) | ||||
@@ -213,7 +245,7 @@ namespace Discord.Commands | |||||
continue; | 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.IsSuccess) | ||||
{ | { | ||||
if (parseResult.Error == CommandError.MultipleMatches) | 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 System.Threading.Tasks; | ||||
using Discord.Commands.Builders; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||||
public class CommandParameter | |||||
public class ParameterInfo | |||||
{ | { | ||||
private readonly TypeReader _reader; | 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 Name { get; } | ||||
public string Summary { get; } | public string Summary { get; } | ||||
public bool IsOptional { get; } | public bool IsOptional { get; } | ||||
public bool IsRemainder { get; } | public bool IsRemainder { get; } | ||||
public bool IsMultiple { get; } | public bool IsMultiple { get; } | ||||
public Type ElementType { get; } | |||||
public Type Type { get; } | |||||
public object DefaultValue { 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) | public async Task<TypeReaderResult> Parse(CommandContext context, string input) | ||||
{ | { | ||||
return await _reader.Read(context, input).ConfigureAwait(false); | return await _reader.Read(context, input).ConfigureAwait(false); | ||||
@@ -40,4 +43,4 @@ namespace Discord.Commands | |||||
public override string ToString() => Name; | public override string ToString() => Name; | ||||
private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}"; | 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 | namespace Discord.Commands | ||||
{ | { | ||||
internal class CommandMap | 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() | public CommandMap() | ||||
{ | { | ||||
_nodes = new ConcurrentDictionary<string, CommandMapNode>(); | |||||
_root = new CommandMapNode(""); | |||||
} | } | ||||
public void AddCommand(CommandInfo command) | 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) | 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) | 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.Generic; | ||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
@@ -6,6 +7,8 @@ namespace Discord.Commands | |||||
{ | { | ||||
internal class CommandMapNode | internal class CommandMapNode | ||||
{ | { | ||||
private static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' }; | |||||
private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | ||||
private readonly string _name; | private readonly string _name; | ||||
private readonly object _lockObj = new object(); | private readonly object _lockObj = new object(); | ||||
@@ -22,13 +25,17 @@ namespace Discord.Commands | |||||
public void AddCommand(string text, int index, CommandInfo command) | public void AddCommand(string text, int index, CommandInfo command) | ||||
{ | { | ||||
int nextSpace = text.IndexOf(' ', index); | |||||
int nextSpace = NextWhitespace(text, index); | |||||
string name; | string name; | ||||
lock (_lockObj) | lock (_lockObj) | ||||
{ | { | ||||
if (text == "") | if (text == "") | ||||
{ | |||||
if (_name == "") | |||||
throw new InvalidOperationException("Cannot add commands to the root node."); | |||||
_commands = _commands.Add(command); | _commands = _commands.Add(command); | ||||
} | |||||
else | else | ||||
{ | { | ||||
if (nextSpace == -1) | if (nextSpace == -1) | ||||
@@ -43,7 +50,7 @@ namespace Discord.Commands | |||||
} | } | ||||
public void RemoveCommand(string text, int index, CommandInfo command) | public void RemoveCommand(string text, int index, CommandInfo command) | ||||
{ | { | ||||
int nextSpace = text.IndexOf(' ', index); | |||||
int nextSpace = NextWhitespace(text, index); | |||||
string name; | string name; | ||||
lock (_lockObj) | lock (_lockObj) | ||||
@@ -70,7 +77,7 @@ namespace Discord.Commands | |||||
public IEnumerable<CommandInfo> GetCommands(string text, int index) | public IEnumerable<CommandInfo> GetCommands(string text, int index) | ||||
{ | { | ||||
int nextSpace = text.IndexOf(' ', index); | |||||
int nextSpace = NextWhitespace(text, index); | |||||
string name; | string name; | ||||
var commands = _commands; | 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; } | 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 byNameBuilder = ImmutableDictionary.CreateBuilder<string, object>(); | ||||
var byValueBuilder = ImmutableDictionary.CreateBuilder<T, 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(); | _enumsByName = byNameBuilder.ToImmutable(); | ||||
@@ -2,6 +2,7 @@ | |||||
{ | { | ||||
public enum RunMode | public enum RunMode | ||||
{ | { | ||||
Default, | |||||
Sync, | Sync, | ||||
Mixed, | Mixed, | ||||
Async | Async | ||||
@@ -18,7 +18,7 @@ namespace Discord.Commands | |||||
throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); | throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); | ||||
var constructor = constructors[0]; | var constructor = constructors[0]; | ||||
ParameterInfo[] parameters = constructor.GetParameters(); | |||||
System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); | |||||
return (map) => | 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.", | "description": "A Discord.Net extension adding support for bot commands.", | ||||
"authors": [ "RogueException" ], | "authors": [ "RogueException" ], | ||||
@@ -40,4 +40,4 @@ | |||||
] | ] | ||||
} | } | ||||
} | } | ||||
} | |||||
} |
@@ -1,4 +1,5 @@ | |||||
#pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
using System; | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
namespace Discord.API | namespace Discord.API | ||||
@@ -13,9 +14,23 @@ namespace Discord.API | |||||
public string Description { get; set; } | public string Description { get; set; } | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | 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")] | [JsonProperty("thumbnail")] | ||||
public Optional<EmbedThumbnail> Thumbnail { get; set; } | public Optional<EmbedThumbnail> Thumbnail { get; set; } | ||||
[JsonProperty("image")] | |||||
public Optional<EmbedImage> Image { get; set; } | |||||
[JsonProperty("provider")] | [JsonProperty("provider")] | ||||
public Optional<EmbedProvider> Provider { get; set; } | 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; | _restClientProvider = restClientProvider; | ||||
_userAgent = userAgent; | _userAgent = userAgent; | ||||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||||
_serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() }; | |||||
RequestQueue = requestQueue; | RequestQueue = requestQueue; | ||||
FetchCurrentUser = true; | FetchCurrentUser = true; | ||||
@@ -165,30 +165,30 @@ namespace Discord.API | |||||
//Core | //Core | ||||
internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids, | 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, | 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 = options ?? new RequestOptions(); | ||||
options.HeaderOnly = true; | 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); | var request = new RestRequest(_restClient, method, endpoint, options); | ||||
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); | await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); | ||||
} | } | ||||
internal Task SendJsonAsync(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids, | 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, | 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 = options ?? new RequestOptions(); | ||||
options.HeaderOnly = true; | 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 json = payload != null ? SerializeJson(payload) : null; | ||||
var request = new JsonRestRequest(_restClient, method, endpoint, json, options); | 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, | 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, | 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 = options ?? new RequestOptions(); | ||||
options.HeaderOnly = true; | 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); | var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); | ||||
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); | await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); | ||||
} | } | ||||
internal Task<TResponse> SendAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, BucketIds ids, | 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, | 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 = 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); | var request = new RestRequest(_restClient, method, endpoint, options); | ||||
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); | 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, | 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, | 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 = 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 json = payload != null ? SerializeJson(payload) : null; | ||||
var request = new JsonRestRequest(_restClient, method, endpoint, json, options); | 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, | 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, | 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 = 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); | var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); | ||||
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); | return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); | ||||
@@ -432,20 +432,22 @@ namespace Discord.API | |||||
if (relativeId != null) | if (relativeId != null) | ||||
endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; | endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; | ||||
else | 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); | return await SendAsync<IReadOnlyCollection<Message>>("GET", endpoint, ids, options: options).ConfigureAwait(false); | ||||
} | } | ||||
public async Task<Message> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) | public async Task<Message> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) | ||||
{ | { | ||||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | Preconditions.NotEqual(channelId, 0, nameof(channelId)); | ||||
Preconditions.NotNull(args, nameof(args)); | 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) | if (args.Content.Length > DiscordConfig.MaxMessageSize) | ||||
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | ||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
var ids = new BucketIds(channelId: channelId); | 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) | 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); | 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) | public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | ||||
{ | { | ||||
@@ -496,21 +498,22 @@ namespace Discord.API | |||||
break; | 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(channelId, 0, nameof(channelId)); | ||||
Preconditions.NotEqual(messageId, 0, nameof(messageId)); | Preconditions.NotEqual(messageId, 0, nameof(messageId)); | ||||
Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
if (args.Content.IsSpecified) | 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) | if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) | ||||
throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | ||||
} | } | ||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
var ids = new BucketIds(channelId: channelId); | 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) | 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)) | using (JsonReader reader = new JsonTextReader(text)) | ||||
return _serializer.Deserialize<T>(reader); | 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 | internal class BucketIds | ||||
{ | { | ||||
@@ -1,4 +1,5 @@ | |||||
#pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
using System; | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
@@ -13,6 +14,8 @@ namespace Discord.API.Rest | |||||
public Optional<string> Nonce { get; set; } | public Optional<string> Nonce { get; set; } | ||||
[JsonProperty("tts")] | [JsonProperty("tts")] | ||||
public Optional<bool> IsTTS { get; set; } | public Optional<bool> IsTTS { get; set; } | ||||
[JsonProperty("embed")] | |||||
public Optional<Embed> Embed { get; set; } | |||||
public CreateMessageParams(string content) | public CreateMessageParams(string content) | ||||
{ | { | ||||
@@ -8,5 +8,7 @@ namespace Discord.API.Rest | |||||
{ | { | ||||
[JsonProperty("content")] | [JsonProperty("content")] | ||||
public Optional<string> Content { get; set; } | 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"; | "Unknown"; | ||||
public static readonly string ClientAPIUrl = $"https://discordapp.com/api/v{APIVersion}/"; | 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 string InviteUrl = "https://discord.gg/"; | ||||
public const int DefaultRequestTimeout = 15000; | |||||
public const int MaxMessageSize = 2000; | public const int MaxMessageSize = 2000; | ||||
public const int MaxMessagesPerBatch = 100; | public const int MaxMessagesPerBatch = 100; | ||||
public const int MaxUsersPerBatch = 1000; | public const int MaxUsersPerBatch = 1000; | ||||
@@ -8,7 +8,7 @@ namespace Discord | |||||
public interface IMessageChannel : IChannel | public interface IMessageChannel : IChannel | ||||
{ | { | ||||
/// <summary> Sends a message to this message channel. </summary> | /// <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> | /// <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); | 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> | /// <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); | 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 | public interface IEmbed | ||||
{ | { | ||||
@@ -6,7 +9,14 @@ | |||||
string Type { get; } | string Type { get; } | ||||
string Title { get; } | string Title { get; } | ||||
string Description { 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; } | EmbedProvider? Provider { get; } | ||||
EmbedThumbnail? Thumbnail { 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.Collections.Generic; | ||||
using System.Threading.Tasks; | 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 | 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) | 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 = | RawValue = | ||||
((uint)(r * 255.0f) << 16) | | ((uint)(r * 255.0f) << 16) | | ||||
((uint)(g * 255.0f) << 8) | | ((uint)(g * 255.0f) << 8) | | ||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
public interface IRole : ISnowflakeEntity, IDeletable, IMentionable | |||||
public interface IRole : ISnowflakeEntity, IDeletable, IMentionable, IComparable<IRole> | |||||
{ | { | ||||
/// <summary> Gets the guild owning this role.</summary> | /// <summary> Gets the guild owning this role.</summary> | ||||
IGuild Guild { get; } | IGuild Guild { get; } | ||||
@@ -27,4 +27,4 @@ namespace Discord | |||||
///// <summary> Modifies this role. </summary> | ///// <summary> Modifies this role. </summary> | ||||
Task ModifyAsync(Action<ModifyGuildRoleParams> func, RequestOptions options = null); | Task ModifyAsync(Action<ModifyGuildRoleParams> func, RequestOptions options = null); | ||||
} | } | ||||
} | |||||
} |
@@ -6,14 +6,23 @@ namespace Discord | |||||
{ | { | ||||
public static class GuildUserExtensions | public static class GuildUserExtensions | ||||
{ | { | ||||
//TODO: Should we remove Add/Remove? Encourages race conditions. | |||||
public static Task AddRolesAsync(this IGuildUser user, params IRole[] roles) | 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) | 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) | 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) | 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 | 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() | 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 WindowCount { get; } | ||||
public int WindowSeconds { 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; | WindowCount = count; | ||||
WindowSeconds = seconds; | WindowSeconds = seconds; | ||||
} | } | ||||
@@ -79,7 +79,9 @@ namespace Discord.Net.Queue | |||||
int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds); | int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds); | ||||
if (millis > 0) | if (millis > 0) | ||||
{ | { | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]"); | Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]"); | ||||
#endif | |||||
await Task.Delay(millis).ConfigureAwait(false); | 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 Newtonsoft.Json.Linq; | ||||
using System; | using System; | ||||
#if DEBUG_LIMITS | |||||
using System.Diagnostics; | using System.Diagnostics; | ||||
#endif | |||||
using System.IO; | using System.IO; | ||||
using System.Net; | using System.Net; | ||||
using System.Threading; | using System.Threading; | ||||
@@ -27,8 +30,8 @@ namespace Discord.Net.Queue | |||||
_lock = new object(); | _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 | else | ||||
WindowCount = 1; //Only allow one request until we get a header back | WindowCount = 1; //Only allow one request until we get a header back | ||||
_semaphore = WindowCount; | _semaphore = WindowCount; | ||||
@@ -40,62 +43,91 @@ namespace Discord.Net.Queue | |||||
public async Task<Stream> SendAsync(RestRequest request) | public async Task<Stream> SendAsync(RestRequest request) | ||||
{ | { | ||||
int id = Interlocked.Increment(ref nextId); | int id = Interlocked.Increment(ref nextId); | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Start"); | Debug.WriteLine($"[{id}] Start"); | ||||
#endif | |||||
LastAttemptAt = DateTimeOffset.UtcNow; | LastAttemptAt = DateTimeOffset.UtcNow; | ||||
while (true) | while (true) | ||||
{ | { | ||||
await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false); | await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false); | ||||
await EnterAsync(id, request).ConfigureAwait(false); | await EnterAsync(id, request).ConfigureAwait(false); | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Sending..."); | 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); | UpdateRateLimit(id, request, info, lag, false); | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Stop"); | Debug.WriteLine($"[{id}] Stop"); | ||||
return response.Stream; | |||||
#endif | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -135,7 +167,9 @@ namespace Discord.Net.Queue | |||||
if (resetAt > timeoutAt) | if (resetAt > timeoutAt) | ||||
throw new RateLimitedException(); | throw new RateLimitedException(); | ||||
int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); | int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); | Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); | ||||
#endif | |||||
if (millis > 0) | if (millis > 0) | ||||
await Task.Delay(millis, request.CancelToken).ConfigureAwait(false); | await Task.Delay(millis, request.CancelToken).ConfigureAwait(false); | ||||
} | } | ||||
@@ -143,13 +177,17 @@ namespace Discord.Net.Queue | |||||
{ | { | ||||
if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0) | if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0) | ||||
throw new RateLimitedException(); | throw new RateLimitedException(); | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)"); | Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)"); | ||||
#endif | |||||
await Task.Delay(500, request.CancelToken).ConfigureAwait(false); | await Task.Delay(500, request.CancelToken).ConfigureAwait(false); | ||||
} | } | ||||
continue; | continue; | ||||
} | } | ||||
#if DEBUG_LIMITS | |||||
else | else | ||||
Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)"); | Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)"); | ||||
#endif | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
@@ -166,7 +204,9 @@ namespace Discord.Net.Queue | |||||
{ | { | ||||
WindowCount = info.Limit.Value; | WindowCount = info.Limit.Value; | ||||
_semaphore = info.Remaining.Value; | _semaphore = info.Remaining.Value; | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}"); | Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}"); | ||||
#endif | |||||
} | } | ||||
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); | var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); | ||||
@@ -182,24 +222,32 @@ namespace Discord.Net.Queue | |||||
{ | { | ||||
//RetryAfter is more accurate than Reset, where available | //RetryAfter is more accurate than Reset, where available | ||||
resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value); | resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value); | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); | Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); | ||||
#endif | |||||
} | } | ||||
else if (info.Reset.HasValue) | else if (info.Reset.HasValue) | ||||
{ | { | ||||
resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds); | resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds); | ||||
int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; | 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)"); | 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) | if (resetTick == null) | ||||
{ | { | ||||
WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token) | WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token) | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] Disabled Semaphore"); | Debug.WriteLine($"[{id}] Disabled Semaphore"); | ||||
#endif | |||||
return; | return; | ||||
} | } | ||||
@@ -207,7 +255,9 @@ namespace Discord.Net.Queue | |||||
{ | { | ||||
_resetTick = resetTick; | _resetTick = resetTick; | ||||
LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset | 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"); | Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms"); | ||||
#endif | |||||
if (!hasQueuedReset) | if (!hasQueuedReset) | ||||
{ | { | ||||
@@ -227,7 +277,9 @@ namespace Discord.Net.Queue | |||||
millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds); | millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds); | ||||
if (millis <= 0) //Make sure we havent gotten a more accurate reset time | if (millis <= 0) //Make sure we havent gotten a more accurate reset time | ||||
{ | { | ||||
#if DEBUG_LIMITS | |||||
Debug.WriteLine($"[{id}] * Reset *"); | Debug.WriteLine($"[{id}] * Reset *"); | ||||
#endif | |||||
_semaphore = WindowCount; | _semaphore = WindowCount; | ||||
_resetTick = null; | _resetTick = null; | ||||
return; | return; | ||||
@@ -236,4 +288,4 @@ namespace Discord.Net.Queue | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | |||||
} |
@@ -120,7 +120,7 @@ namespace Discord.Net.Rest | |||||
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token; | cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token; | ||||
HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); | 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; | var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null; | ||||
return new RestResponse(response.StatusCode, headers, stream); | return new RestResponse(response.StatusCode, headers, stream); | ||||
@@ -101,6 +101,8 @@ namespace Discord.Net.WebSockets | |||||
if (_client != null && _client.State == WebSocketState.Open) | if (_client != null && _client.State == WebSocketState.Open) | ||||
{ | { | ||||
var token = new CancellationToken(); | |||||
await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", token); | |||||
_client.Dispose(); | _client.Dispose(); | ||||
_client = null; | _client = null; | ||||
} | } | ||||
@@ -10,7 +10,7 @@ | |||||
internal bool IgnoreState { get; set; } | internal bool IgnoreState { get; set; } | ||||
internal string BucketId { get; set; } | internal string BucketId { get; set; } | ||||
internal string ClientBucketId { get; set; } | |||||
internal bool IsClientBucket { get; set; } | |||||
internal static RequestOptions CreateOrClone(RequestOptions options) | internal static RequestOptions CreateOrClone(RequestOptions options) | ||||
{ | { | ||||
@@ -22,7 +22,7 @@ | |||||
public RequestOptions() | public RequestOptions() | ||||
{ | { | ||||
Timeout = 30000; | |||||
Timeout = DiscordConfig.DefaultRequestTimeout; | |||||
} | } | ||||
public RequestOptions Clone() => MemberwiseClone() as RequestOptions; | public RequestOptions Clone() => MemberwiseClone() as RequestOptions; | ||||
@@ -85,14 +85,17 @@ namespace Discord | |||||
return false; | 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; | var tags = msg.Tags; | ||||
int indexOffset = 0; | |||||
int indexOffset = -startIndex; | |||||
foreach (var tag in tags) | foreach (var tag in tags) | ||||
{ | { | ||||
if (tag.Index < startIndex) | |||||
continue; | |||||
string newText = ""; | string newText = ""; | ||||
switch (tag.Type) | switch (tag.Type) | ||||
{ | { | ||||
@@ -139,12 +142,22 @@ namespace Discord | |||||
if (user != null) | if (user != null) | ||||
return $"@{guildUser?.Nickname ?? user?.Username}"; | return $"@{guildUser?.Nickname ?? user?.Username}"; | ||||
else | else | ||||
return $"@unknown-user"; | |||||
return $""; | |||||
case TagHandling.NameNoPrefix: | |||||
if (user != null) | |||||
return $"{guildUser?.Nickname ?? user?.Username}"; | |||||
else | |||||
return $""; | |||||
case TagHandling.FullName: | case TagHandling.FullName: | ||||
if (user != null) | if (user != null) | ||||
return $"@{user.Username}#{user.Discriminator}"; | return $"@{user.Username}#{user.Discriminator}"; | ||||
else | else | ||||
return $"@unknown-user"; | |||||
return $""; | |||||
case TagHandling.FullNameNoPrefix: | |||||
if (user != null) | |||||
return $"{user.Username}#{user.Discriminator}"; | |||||
else | |||||
return $""; | |||||
case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
if (guildUser != null && guildUser.Nickname == null) | if (guildUser != null && guildUser.Nickname == null) | ||||
return MentionUser($"{SanitizeChar}{tag.Key}", false); | return MentionUser($"{SanitizeChar}{tag.Key}", false); | ||||
@@ -166,7 +179,13 @@ namespace Discord | |||||
if (channel != null) | if (channel != null) | ||||
return $"#{channel.Name}"; | return $"#{channel.Name}"; | ||||
else | else | ||||
return $"#deleted-channel"; | |||||
return $""; | |||||
case TagHandling.NameNoPrefix: | |||||
case TagHandling.FullNameNoPrefix: | |||||
if (channel != null) | |||||
return $"{channel.Name}"; | |||||
else | |||||
return $""; | |||||
case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
return MentionChannel($"{SanitizeChar}{tag.Key}"); | return MentionChannel($"{SanitizeChar}{tag.Key}"); | ||||
} | } | ||||
@@ -185,7 +204,13 @@ namespace Discord | |||||
if (role != null) | if (role != null) | ||||
return $"@{role.Name}"; | return $"@{role.Name}"; | ||||
else | else | ||||
return $"@deleted-role"; | |||||
return $""; | |||||
case TagHandling.NameNoPrefix: | |||||
case TagHandling.FullNameNoPrefix: | |||||
if (role != null) | |||||
return $"{role.Name}"; | |||||
else | |||||
return $""; | |||||
case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
return MentionRole($"{SanitizeChar}{tag.Key}"); | return MentionRole($"{SanitizeChar}{tag.Key}"); | ||||
} | } | ||||
@@ -200,7 +225,9 @@ namespace Discord | |||||
{ | { | ||||
case TagHandling.Name: | case TagHandling.Name: | ||||
case TagHandling.FullName: | case TagHandling.FullName: | ||||
return "@everyone"; | |||||
case TagHandling.NameNoPrefix: | |||||
case TagHandling.FullNameNoPrefix: | |||||
return "everyone"; | |||||
case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
return $"@{SanitizeChar}everyone"; | return $"@{SanitizeChar}everyone"; | ||||
} | } | ||||
@@ -215,9 +242,11 @@ namespace Discord | |||||
{ | { | ||||
case TagHandling.Name: | case TagHandling.Name: | ||||
case TagHandling.FullName: | case TagHandling.FullName: | ||||
return "@everyone"; | |||||
case TagHandling.NameNoPrefix: | |||||
case TagHandling.FullNameNoPrefix: | |||||
return "here"; | |||||
case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
return $"@{SanitizeChar}everyone"; | |||||
return $"@{SanitizeChar}here"; | |||||
} | } | ||||
} | } | ||||
return ""; | return ""; | ||||
@@ -232,8 +261,11 @@ namespace Discord | |||||
case TagHandling.Name: | case TagHandling.Name: | ||||
case TagHandling.FullName: | case TagHandling.FullName: | ||||
return $":{emoji.Name}:"; | return $":{emoji.Name}:"; | ||||
case TagHandling.NameNoPrefix: | |||||
case TagHandling.FullNameNoPrefix: | |||||
return $"{emoji.Name}"; | |||||
case TagHandling.Sanitize: | case TagHandling.Sanitize: | ||||
return $"<@{SanitizeChar}everyone"; | |||||
return $"<{emoji.Id}{SanitizeChar}:{SanitizeChar}{emoji.Name}>"; | |||||
} | } | ||||
} | } | ||||
return ""; | return ""; | ||||
@@ -5,148 +5,182 @@ namespace Discord | |||||
internal static class Preconditions | internal static class Preconditions | ||||
{ | { | ||||
//Objects | //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 | //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.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.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 | //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.", | "description": "A .Net API wrapper and bot framework for Discord.", | ||||
"authors": [ "RogueException" ], | "authors": [ "RogueException" ], | ||||
@@ -26,14 +26,14 @@ | |||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"Microsoft.Win32.Primitives": "4.0.1", | |||||
"Microsoft.Win32.Primitives": "4.3.0", | |||||
"Newtonsoft.Json": "9.0.1", | "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": { | "System.Net.WebSockets.Client": { | ||||
"version": "4.0.0", | |||||
"version": "4.3.0", | |||||
"type": "build" | "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 | public class DiscordRestClient : BaseDiscordClient, IDiscordClient | ||||
{ | { | ||||
private RestApplication _applicationInfo; | |||||
public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | ||||
public DiscordRestClient() : this(new DiscordRestConfig()) { } | public DiscordRestClient() : this(new DiscordRestConfig()) { } | ||||
@@ -21,10 +23,17 @@ namespace Discord.Rest | |||||
base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); | base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); | ||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
protected override Task OnLogoutAsync() | |||||
{ | |||||
_applicationInfo = null; | |||||
return Task.CompletedTask; | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task<RestApplication> GetApplicationInfoAsync() | |||||
=> ClientHelper.GetApplicationInfoAsync(this); | |||||
public async Task<RestApplication> GetApplicationInfoAsync() | |||||
{ | |||||
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this)); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task<RestChannel> GetChannelAsync(ulong id) | 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})"; | 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 MessageQueueInterval = 100; | ||||
internal const int WebSocketQueueInterval = 100; | internal const int WebSocketQueueInterval = 100; | ||||
@@ -6,6 +6,7 @@ using System.IO; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
using UserModel = Discord.API.User; | |||||
namespace Discord.Rest | namespace Discord.Rest | ||||
{ | { | ||||
@@ -43,13 +44,13 @@ namespace Discord.Rest | |||||
} | } | ||||
//Invites | //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) | RequestOptions options) | ||||
{ | { | ||||
var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options).ConfigureAwait(false); | var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options).ConfigureAwait(false); | ||||
return models.Select(x => RestInviteMetadata.Create(client, null, channel, x)).ToImmutableArray(); | 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) | int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) | ||||
{ | { | ||||
var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; | var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; | ||||
@@ -62,18 +63,24 @@ namespace Discord.Rest | |||||
} | } | ||||
//Messages | //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); | 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) | if (dir == Direction.Around) | ||||
throw new NotImplementedException(); //TODO: Impl | 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>( | return new PagedAsyncEnumerable<RestMessage>( | ||||
DiscordConfig.MaxMessagesPerBatch, | DiscordConfig.MaxMessagesPerBatch, | ||||
async (info, ct) => | async (info, ct) => | ||||
@@ -85,8 +92,15 @@ namespace Discord.Rest | |||||
}; | }; | ||||
if (info.Position != null) | if (info.Position != null) | ||||
args.RelativeMessageId = info.Position.Value; | args.RelativeMessageId = info.Position.Value; | ||||
var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options).ConfigureAwait(false); | var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
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) => | nextPage: (info, lastPage) => | ||||
{ | { | ||||
@@ -102,37 +116,45 @@ namespace Discord.Rest | |||||
count: limit | 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); | 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); | 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); | string filename = Path.GetFileName(filePath); | ||||
using (var file = File.OpenRead(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 args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | 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) | IEnumerable<IMessage> messages, RequestOptions options) | ||||
{ | { | ||||
var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); | 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, | public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, | ||||
RequestOptions options) | RequestOptions options) | ||||
=> new TypingNotifier(client, channel, 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 | public interface IRestMessageChannel : IMessageChannel | ||||
{ | { | ||||
/// <summary> Sends a message to this message channel. </summary> | /// <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> | /// <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); | 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> | /// <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) | 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) | 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) | 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) | 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) | 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) | 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) | 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) | public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | ||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | ||||
@@ -126,8 +126,8 @@ namespace Discord.Rest | |||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | ||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | => 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) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
=> EnterTypingState(options); | => EnterTypingState(options); | ||||
@@ -66,22 +66,22 @@ namespace Discord.Rest | |||||
} | } | ||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | 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) | 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) | 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) | 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) | 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) | 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) | 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) | public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | ||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | ||||
@@ -136,8 +136,8 @@ namespace Discord.Rest | |||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | ||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | => 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) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
=> EnterTypingState(options); | => EnterTypingState(options); | ||||
@@ -45,22 +45,22 @@ namespace Discord.Rest | |||||
=> ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); | => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); | ||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | 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) | 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) | 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) | 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) | 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) | 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) | 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) | public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | ||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | ||||
@@ -108,8 +108,8 @@ namespace Discord.Rest | |||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | ||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | => 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) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
=> EnterTypingState(options); | => EnterTypingState(options); | ||||
@@ -23,22 +23,22 @@ namespace Discord.Rest | |||||
} | } | ||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | 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) | 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) | 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) | 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) | 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) | 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) | 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) | public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | ||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | ||||
@@ -86,8 +86,8 @@ namespace Discord.Rest | |||||
=> await SendFileAsync(filePath, text, isTTS, options); | => await SendFileAsync(filePath, text, isTTS, options); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | ||||
=> await SendFileAsync(stream, filename, text, isTTS, options); | => await SendFileAsync(stream, filename, text, isTTS, options); | ||||
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) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
=> EnterTypingState(options); | => EnterTypingState(options); | ||||
@@ -142,7 +142,7 @@ namespace Discord.Rest | |||||
if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false); | 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 => | await role.ModifyAsync(x => | ||||
{ | { | ||||
@@ -87,7 +87,7 @@ namespace Discord.Rest | |||||
if (model.Roles != null) | if (model.Roles != null) | ||||
{ | { | ||||
for (int i = 0; i < model.Roles.Length; i++) | 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(); | _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; | using Model = Discord.API.Embed; | ||||
namespace Discord | namespace Discord | ||||
@@ -10,23 +13,55 @@ namespace Discord | |||||
public string Url { get; } | public string Url { get; } | ||||
public string Title { get; } | public string Title { get; } | ||||
public string Type { 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 EmbedProvider? Provider { get; } | ||||
public EmbedThumbnail? Thumbnail { 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; | Type = type; | ||||
Title = title; | Title = title; | ||||
Description = description; | Description = description; | ||||
Url = url; | Url = url; | ||||
Color = color; | |||||
Timestamp = timestamp; | |||||
Image = image; | |||||
Video = video; | |||||
Author = author; | |||||
Footer = footer; | |||||
Provider = provider; | Provider = provider; | ||||
Thumbnail = thumbnail; | Thumbnail = thumbnail; | ||||
Fields = fields; | |||||
} | } | ||||
internal static Embed Create(Model model) | 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.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; | public override string ToString() => Title; | ||||
@@ -15,7 +15,12 @@ namespace Discord.Rest | |||||
{ | { | ||||
var args = new ModifyMessageParams(); | var args = new ModifyMessageParams(); | ||||
func(args); | 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, | public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, | ||||
RequestOptions options) | RequestOptions options) | ||||
@@ -9,11 +9,10 @@ namespace Discord.Rest | |||||
{ | { | ||||
public abstract class RestMessage : RestEntity<ulong>, IMessage, IUpdateable | public abstract class RestMessage : RestEntity<ulong>, IMessage, IUpdateable | ||||
{ | { | ||||
internal readonly IGuild _guild; | |||||
private long _timestampTicks; | private long _timestampTicks; | ||||
public IMessageChannel Channel { get; } | public IMessageChannel Channel { get; } | ||||
public RestUser Author { get; } | |||||
public IUser Author { get; } | |||||
public string Content { get; private set; } | public string Content { get; private set; } | ||||
@@ -32,19 +31,18 @@ namespace Discord.Rest | |||||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | 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) | : base(discord, id) | ||||
{ | { | ||||
Channel = channel; | Channel = channel; | ||||
Author = author; | 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) | if (model.Type == MessageType.Default) | ||||
return RestUserMessage.Create(discord, guild, model); | |||||
return RestUserMessage.Create(discord, channel, author, model); | |||||
else | else | ||||
return RestSystemMessage.Create(discord, guild, model); | |||||
return RestSystemMessage.Create(discord, channel, author, model); | |||||
} | } | ||||
internal virtual void Update(Model model) | internal virtual void Update(Model model) | ||||
{ | { | ||||
@@ -8,15 +8,13 @@ namespace Discord.Rest | |||||
{ | { | ||||
public MessageType Type { get; private set; } | 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); | entity.Update(model); | ||||
return entity; | return entity; | ||||
} | } | ||||
@@ -3,7 +3,6 @@ using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Linq; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Model = Discord.API.Message; | using Model = Discord.API.Message; | ||||
@@ -32,15 +31,13 @@ namespace Discord.Rest | |||||
public override IReadOnlyCollection<ITag> Tags => _tags; | public override IReadOnlyCollection<ITag> Tags => _tags; | ||||
public IReadOnlyDictionary<Emoji, int> Reactions => _reactions.ToDictionary(x => x.Emoji, x => x.Count); | 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); | entity.Update(model); | ||||
return entity; | return entity; | ||||
} | } | ||||
@@ -122,7 +119,9 @@ namespace Discord.Rest | |||||
if (model.Content.IsSpecified) | if (model.Content.IsSpecified) | ||||
{ | { | ||||
var text = model.Content.Value; | 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; | model.Content = text; | ||||
} | } | ||||
} | } | ||||
@@ -155,9 +154,12 @@ namespace Discord.Rest | |||||
public Task UnpinAsync(RequestOptions options) | public Task UnpinAsync(RequestOptions options) | ||||
=> MessageHelper.UnpinAsync(this, Discord, 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, | public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | ||||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = 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" : "")})"; | private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; | ||||
} | } | ||||
@@ -9,7 +9,7 @@ namespace Discord.Rest | |||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
public class RestRole : RestEntity<ulong>, IRole | public class RestRole : RestEntity<ulong>, IRole | ||||
{ | { | ||||
public RestGuild Guild { get; } | |||||
internal IGuild Guild { get; } | |||||
public Color Color { get; private set; } | public Color Color { get; private set; } | ||||
public bool IsHoisted { get; private set; } | public bool IsHoisted { get; private set; } | ||||
public bool IsManaged { get; private set; } | public bool IsManaged { get; private set; } | ||||
@@ -22,13 +22,14 @@ namespace Discord.Rest | |||||
public bool IsEveryone => Id == Guild.Id; | public bool IsEveryone => Id == Guild.Id; | ||||
public string Mention => MentionUtils.MentionRole(Id); | public string Mention => MentionUtils.MentionRole(Id); | ||||
internal RestRole(BaseDiscordClient discord, ulong id) | |||||
internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) | |||||
: base(discord, 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); | entity.Update(model); | ||||
return entity; | return entity; | ||||
} | } | ||||
@@ -51,10 +52,20 @@ namespace Discord.Rest | |||||
public Task DeleteAsync(RequestOptions options = null) | public Task DeleteAsync(RequestOptions options = null) | ||||
=> RoleHelper.DeleteAsync(this, Discord, options); | => RoleHelper.DeleteAsync(this, Discord, options); | ||||
public int CompareTo(IRole role) => this.Compare(role); | |||||
public override string ToString() => Name; | public override string ToString() => Name; | ||||
private string DebuggerDisplay => $"{Name} ({Id})"; | private string DebuggerDisplay => $"{Name} ({Id})"; | ||||
//IRole | //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.", | "description": "A core Discord.Net library containing the REST client and models.", | ||||
"authors": [ "RogueException" ], | "authors": [ "RogueException" ], | ||||
@@ -29,7 +29,7 @@ | |||||
"Discord.Net.Core": { | "Discord.Net.Core": { | ||||
"target": "project" | "target": "project" | ||||
}, | }, | ||||
"System.IO.FileSystem": "4.0.1" | |||||
"System.IO.FileSystem": "4.3.0" | |||||
}, | }, | ||||
"frameworks": { | "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 | namespace Discord.Rpc | ||||
{ | { | ||||
public partial class DiscordRpcClient : BaseDiscordClient | |||||
public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient | |||||
{ | { | ||||
private readonly Logger _rpcLogger; | private readonly Logger _rpcLogger; | ||||
private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
@@ -33,7 +33,7 @@ namespace Discord.Rpc | |||||
public new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient; | 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 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> | /// <summary> Creates a new RPC discord client. </summary> | ||||
public DiscordRpcClient(string clientId, string origin) | public DiscordRpcClient(string clientId, string origin) | ||||
@@ -393,7 +393,7 @@ namespace Discord.Rpc | |||||
{ | { | ||||
var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); | var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); | ||||
CurrentUser = RestSelfUser.Create(this, response.User); | CurrentUser = RestSelfUser.Create(this, response.User); | ||||
CurrentApplication = RestApplication.Create(this, response.Application); | |||||
ApplicationInfo = RestApplication.Create(this, response.Application); | |||||
Scopes = response.Scopes; | Scopes = response.Scopes; | ||||
TokenExpiresAt = response.Expires; | TokenExpiresAt = response.Expires; | ||||
@@ -547,5 +547,8 @@ namespace Discord.Rpc | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
//IDiscordClient | |||||
Task<IApplication> IDiscordClient.GetApplicationInfoAsync() => Task.FromResult<IApplication>(ApplicationInfo); | |||||
} | } | ||||
} | } |
@@ -34,22 +34,22 @@ namespace Discord.Rpc | |||||
//TODO: Use RPC cache | //TODO: Use RPC cache | ||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | 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) | 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) | 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) | 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) | 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) | 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) | 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) | public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | ||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | ||||
@@ -104,8 +104,8 @@ namespace Discord.Rpc | |||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | ||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | => 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) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
=> EnterTypingState(options); | => EnterTypingState(options); | ||||
@@ -36,22 +36,22 @@ namespace Discord.Rpc | |||||
//TODO: Use RPC cache | //TODO: Use RPC cache | ||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | 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) | 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) | 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) | 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) | 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) | 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) | 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) | public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | ||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | ||||
@@ -103,8 +103,8 @@ namespace Discord.Rpc | |||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | ||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | => 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) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
=> EnterTypingState(options); | => EnterTypingState(options); | ||||
@@ -39,22 +39,22 @@ namespace Discord.Rpc | |||||
//TODO: Use RPC cache | //TODO: Use RPC cache | ||||
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) | 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) | 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) | 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) | 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) | 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) | 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) | 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) | public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | ||||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); | ||||
@@ -105,8 +105,8 @@ namespace Discord.Rpc | |||||
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) | ||||
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); | => 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) | IDisposable IMessageChannel.EnterTypingState(RequestOptions options) | ||||
=> EnterTypingState(options); | => EnterTypingState(options); | ||||
} | } | ||||
@@ -123,9 +123,12 @@ namespace Discord.Rpc | |||||
public Task UnpinAsync(RequestOptions options) | public Task UnpinAsync(RequestOptions options) | ||||
=> MessageHelper.UnpinAsync(this, Discord, 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, | public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | ||||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = 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" : "")})"; | 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.", | "description": "A core Discord.Net library containing the RPC client and models.", | ||||
"authors": [ "RogueException" ], | "authors": [ "RogueException" ], | ||||
@@ -32,8 +32,8 @@ | |||||
"Discord.Net.Rest": { | "Discord.Net.Rest": { | ||||
"target": "project" | "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": { | "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 int _nextAudioId; | ||||
private bool _canReconnect; | private bool _canReconnect; | ||||
private DateTimeOffset? _statusSince; | private DateTimeOffset? _statusSince; | ||||
private RestApplication _applicationInfo; | |||||
/// <summary> Gets the shard of of this client. </summary> | /// <summary> Gets the shard of of this client. </summary> | ||||
public int ShardId { get; } | public int ShardId { get; } | ||||
@@ -123,6 +124,7 @@ namespace Discord.WebSocket | |||||
if (ConnectionState != ConnectionState.Disconnected) | if (ConnectionState != ConnectionState.Disconnected) | ||||
await DisconnectInternalAsync(null, false).ConfigureAwait(false); | await DisconnectInternalAsync(null, false).ConfigureAwait(false); | ||||
_applicationInfo = null; | |||||
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | ||||
} | } | ||||
@@ -333,8 +335,10 @@ namespace Discord.WebSocket | |||||
} | } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task<RestApplication> GetApplicationInfoAsync() | |||||
=> ClientHelper.GetApplicationInfoAsync(this); | |||||
public async Task<RestApplication> GetApplicationInfoAsync() | |||||
{ | |||||
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this)); | |||||
} | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public SocketGuild GetGuild(ulong id) | public SocketGuild GetGuild(ulong id) | ||||