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