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