@@ -202,4 +202,7 @@ project.lock.json | |||
/docs/_build | |||
*.pyc | |||
/.editorconfig | |||
.vscode/ | |||
.vscode/ | |||
docs/api/\.manifest | |||
\.idea/ |
@@ -1,7 +1,6 @@ | |||
Microsoft Visual Studio Solution File, Format Version 12.00 | |||
# Visual Studio 15 | |||
VisualStudioVersion = 15.0.26014.0 | |||
VisualStudioVersion = 15.0.26228.4 | |||
MinimumVisualStudioVersion = 10.0.40219.1 | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | |||
EndProject | |||
@@ -19,15 +18,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{ | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Providers.WS4Net", "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj", "{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Providers.UdpClient", "src\Discord.Net.Providers.UdpClient\Discord.Net.Providers.UdpClient.csproj", "{ABC9F4B9-2452-4725-B522-754E0A02E282}" | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\Discord.Net.Tests\Discord.Net.Tests.csproj", "{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}" | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F66D75C0-E304-46E0-9C3A-294F340DB37D}" | |||
EndProject | |||
Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "Discord.Net.Relay", "src\Discord.Net.Relay\Discord.Net.Relay.csproj", "{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}" | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
@@ -41,16 +36,16 @@ Global | |||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|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}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x64.Build.0 = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x86.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}.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 | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x64.ActiveCfg = Release|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x64.Build.0 = Release|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x86.ActiveCfg = Release|Any CPU | |||
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x86.Build.0 = Release|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
@@ -89,76 +84,52 @@ Global | |||
{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}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x64.Build.0 = Debug|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x86.Build.0 = Debug|Any CPU | |||
{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 | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x64.ActiveCfg = Release|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x64.Build.0 = Release|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x86.ActiveCfg = Release|Any CPU | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x86.Build.0 = Release|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|x64.ActiveCfg = Debug|x64 | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|x64.Build.0 = Debug|x64 | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|x86.ActiveCfg = Debug|x86 | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|x86.Build.0 = Debug|x86 | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|x64.Build.0 = Debug|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Debug|x86.Build.0 = Debug|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|x64.ActiveCfg = Release|x64 | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|x64.Build.0 = Release|x64 | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|x86.ActiveCfg = Release|x86 | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|x86.Build.0 = Release|x86 | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Debug|x64.ActiveCfg = Debug|x64 | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Debug|x64.Build.0 = Debug|x64 | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Debug|x86.ActiveCfg = Debug|x86 | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Debug|x86.Build.0 = Debug|x86 | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Release|x64.ActiveCfg = Release|x64 | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Release|x64.Build.0 = Release|x64 | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Release|x86.ActiveCfg = Release|x86 | |||
{547261FC-8BA3-40EA-A040-A38ABDAA8D72}.Release|x86.Build.0 = Release|x86 | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Debug|x64.ActiveCfg = Debug|x64 | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Debug|x64.Build.0 = Debug|x64 | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Debug|x86.ActiveCfg = Debug|x86 | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Debug|x86.Build.0 = Debug|x86 | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Release|x64.ActiveCfg = Release|x64 | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Release|x64.Build.0 = Release|x64 | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Release|x86.ActiveCfg = Release|x86 | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282}.Release|x86.Build.0 = Release|x86 | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|x64.ActiveCfg = Release|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|x64.Build.0 = Release|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|x86.ActiveCfg = Release|Any CPU | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}.Release|x86.Build.0 = Release|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|x64.ActiveCfg = Debug|x64 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|x64.Build.0 = Debug|x64 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|x86.ActiveCfg = Debug|x86 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|x86.Build.0 = Debug|x86 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|x64.Build.0 = Debug|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Debug|x86.Build.0 = Debug|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x64.ActiveCfg = Release|x64 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x64.Build.0 = Release|x64 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x86.ActiveCfg = Release|x86 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x86.Build.0 = Release|x86 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|x64.ActiveCfg = Debug|x64 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|x64.Build.0 = Debug|x64 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|x86.ActiveCfg = Debug|x86 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Debug|x86.Build.0 = Debug|x86 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|x64.ActiveCfg = Release|x64 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|x64.Build.0 = Release|x64 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|x86.ActiveCfg = Release|x86 | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B}.Release|x86.Build.0 = Release|x86 | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x64.ActiveCfg = Release|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x64.Build.0 = Release|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x86.ActiveCfg = Release|Any CPU | |||
{C38E5BC1-11CB-4101-8A38-5B40A1BC6433}.Release|x86.Build.0 = Release|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Debug|x64.Build.0 = Debug|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Debug|x86.Build.0 = Debug|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.ActiveCfg = Release|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.Build.0 = Release|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = Release|Any CPU | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.Build.0 = Release|Any CPU | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
@@ -169,7 +140,6 @@ Global | |||
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | |||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | |||
{ABC9F4B9-2452-4725-B522-754E0A02E282} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | |||
{2705FCB3-68C9-4CEB-89CC-01F8EC80512B} = {F66D75C0-E304-46E0-9C3A-294F340DB37D} | |||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | |||
EndGlobalSection | |||
EndGlobal |
@@ -0,0 +1,31 @@ | |||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VersionPrefix>1.0.1</VersionPrefix> | |||
<VersionSuffix></VersionSuffix> | |||
<Authors>RogueException</Authors> | |||
<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> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(BuildNumber)' == '' "> | |||
<VersionSuffix Condition=" '$(VersionSuffix)' != ''">$(VersionSuffix)-dev</VersionSuffix> | |||
<VersionSuffix Condition=" '$(VersionSuffix)' == ''">dev</VersionSuffix> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(BuildNumber)' != '' And $(IsTagBuild) != 'true' "> | |||
<VersionSuffix Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)-$(BuildNumber)</VersionSuffix> | |||
<VersionSuffix Condition=" '$(VersionSuffix)' == '' ">build-$(BuildNumber)</VersionSuffix> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' Or '$(TargetFramework)' == 'net45' "> | |||
<DefineConstants>$(DefineConstants);FILESYSTEM;DEFAULTUDPCLIENT;DEFAULTWEBSOCKET</DefineConstants> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> | |||
<DefineConstants>$(DefineConstants);FORMATSTR;UNIXTIME;MSTRYBUFFER;UDPDISPOSE</DefineConstants> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
</Project> |
@@ -1,4 +1,5 @@ | |||
# Discord.Net v1.0.0-rc | |||
# Discord.Net | |||
[](https://www.nuget.org/packages/Discord.Net) | |||
[](https://www.myget.org/feed/Packages/discord-net) | |||
[](https://ci.appveyor.com/project/RogueException/discord-net/branch/dev) | |||
[](https://discord.gg/0SBTUU1wZTVjAMPx) | |||
@@ -13,13 +14,13 @@ Our stable builds available from NuGet through the Discord.Net metapackage: | |||
- [Discord.Net](https://www.nuget.org/packages/Discord.Net/) | |||
The individual components may also be installed from NuGet: | |||
- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) | |||
- [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/) | |||
- [Discord.Net.Rpc](https://www.nuget.org/packages/Discord.Net.Rpc/) | |||
- [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/) | |||
- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) | |||
- [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/) | |||
The following providers are available for platforms not supporting .NET Standard 1.3: | |||
- [Discord.Net.Providers.UdpClient](https://www.nuget.org/packages/Discord.Net.Providers.UdpClient/) | |||
The following provider is available for platforms not supporting .NET Standard 1.3: | |||
- [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) | |||
### Unstable (MyGet) | |||
@@ -29,13 +30,13 @@ Nightly builds are available through our MyGet feed (`https://www.myget.org/F/di | |||
In order to compile Discord.Net, you require the following: | |||
### Using Visual Studio | |||
- [Visual Studio 2017 RC](https://www.microsoft.com/net/core#windowsvs2017) | |||
- [.NET Core SDK 1.0 RC3](https://github.com/dotnet/core/blob/master/release-notes/rc3-download.md) | |||
- [Visual Studio 2017](https://www.microsoft.com/net/core#windowsvs2017) | |||
- [.NET Core SDK](https://www.microsoft.com/net/download/core) | |||
The .NET Core and Docker (Preview) workload is required during Visual Studio installation. | |||
The .NET Core workload must be selected during Visual Studio installation. | |||
### Using Command Line | |||
- [.NET Core SDK 1.0 RC3](https://github.com/dotnet/core/blob/master/release-notes/rc3-download.md) | |||
- [.NET Core SDK](https://www.microsoft.com/net/download/core) | |||
## Known Issues | |||
@@ -0,0 +1,61 @@ | |||
version: build-{build} | |||
branches: | |||
only: | |||
- dev | |||
image: Visual Studio 2017 | |||
nuget: | |||
disable_publish_on_pr: true | |||
pull_requests: | |||
do_not_increment_build_number: true | |||
clone_folder: C:\Projects\Discord.Net | |||
cache: test/Discord.Net.Tests/cache.db | |||
environment: | |||
DOTNET_CLI_TELEMETRY_OPTOUT: 1 | |||
DNET_TEST_TOKEN: | |||
secure: l7h5e7UE7yRd70hAB97kjPiQpPOShwqoBbOzEAYQ+XBd/Pre5OA33IXa3uisdUeQJP/nPFhcOsI+yn7WpuFaoQ== | |||
DNET_TEST_GUILDID: 273160668180381696 | |||
init: | |||
- ps: $Env:BUILD = "$($Env:APPVEYOR_BUILD_NUMBER.PadLeft(5, "0"))" | |||
build_script: | |||
- ps: appveyor-retry dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
- ps: dotnet build Discord.Net.sln -c "Release" /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
after_build: | |||
- ps: dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
- ps: dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
- ps: dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
- ps: dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
- ps: dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
- ps: dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
- ps: dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
- ps: >- | |||
if ($Env:APPVEYOR_REPO_TAG -eq "true") { | |||
nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" | |||
} else { | |||
nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-build-$Env:BUILD" | |||
} | |||
- ps: Get-ChildItem artifacts\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } | |||
test_script: | |||
- ps: >- | |||
if ($APPVEYOR_PULL_REQUEST_NUMBER -eq "") { | |||
dotnet test test/Discord.Net.Tests/Discord.Net.Tests.csproj -c "Release" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" | |||
} | |||
deploy: | |||
- provider: NuGet | |||
server: https://www.myget.org/F/discord-net/api/v2/package | |||
api_key: | |||
secure: Jl7BXeUjRnkVHDMBuUWSXcEOkrli1PBleW2IiLyUs5j63UNUNp1hcjaUJRujx9lz | |||
symbol_server: https://www.myget.org/F/discord-net/symbols/api/v2/package | |||
on: | |||
branch: dev | |||
- provider: NuGet | |||
server: https://www.myget.org/F/rogueexception/api/v2/package | |||
api_key: | |||
secure: D+vW2O2LBf/iJb4f+q8fkyIW2VdIYIGxSYLWNrOD4BHlDBZQlJipDbNarWjUr2Kn | |||
symbol_server: https://www.myget.org/F/rogueexception/symbols/api/v2/package | |||
on: | |||
branch: dev |
@@ -1,4 +0,0 @@ | |||
appveyor-retry dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } | |||
dotnet build Discord.Net.sln -c "Release" /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } |
@@ -1,15 +1,46 @@ | |||
# Contributing to Docs | |||
I don't really have any strict conditions for writing documentation, but just keep these few guidelines in mind: | |||
I don't really have any strict conditions for writing documentation, | |||
but just keep these few guidelines in mind: | |||
* Keep code samples in the `guides/samples` folder | |||
* When referencing an object in the API, link to it's page in the API documentation. | |||
* When referencing an object in the API, link to it's page in the | |||
API documentation. | |||
* Documentation should be written in clear and proper English* | |||
\* If anyone is interested in translating documentation into other languages, please open an issue or contact me on Discord (`foxbot#0282`). | |||
\* If anyone is interested in translating documentation into other | |||
languages, please open an issue or contact me on | |||
Discord (`foxbot#0282`). | |||
### Layout | |||
Documentation should be written in a FAQ/Wiki style format. | |||
Recommended reads: | |||
* http://docs.microsoft.com | |||
* http://flask.pocoo.org/docs/0.12/ | |||
Style consistencies: | |||
* Use a ruler set at 70 characters | |||
* Links should use long syntax | |||
* Pages should be short and concise, not broad and long | |||
Example of long link syntax: | |||
``` | |||
Please consult the [API Documentation] for more information. | |||
[API Documentation]: xref:System.String | |||
``` | |||
### Compiling | |||
Documentation is compiled into a static site using [DocFx](https://dotnet.github.io/docfx/). We currently use version 2.8 | |||
Documentation is compiled into a static site using [DocFx]. | |||
We currently use the most recent build off the dev branch. | |||
After making changes, compile your changes into the static site with | |||
`docfx`. You can also view your changes live with `docfx --serve`. | |||
After making changes, compile your changes into the static site with `docfx`. You can also view your changes live with `docfx --serve`. | |||
[DocFx]: https://dotnet.github.io/docfx/ |
@@ -5,7 +5,7 @@ | |||
{ | |||
"src": "..", | |||
"files": [ | |||
"src/**project.json" | |||
"src/**/*.cs" | |||
], | |||
"exclude": [ | |||
"**/obj/**", | |||
@@ -42,7 +42,8 @@ | |||
"resource": [ | |||
{ | |||
"files": [ | |||
"images/**" | |||
"**/images/**", | |||
"**/samples/**" | |||
], | |||
"exclude": [ | |||
"obj/**", | |||
@@ -66,7 +67,7 @@ | |||
"default" | |||
], | |||
"globalMetadata": { | |||
"_appFooter": "Discord.Net (c) 2015-2016" | |||
"_appFooter": "Discord.Net (c) 2015-2017" | |||
}, | |||
"noLangKeyword": false | |||
} |
@@ -6,4 +6,6 @@ apiRules: | |||
- exclude: | |||
uidRegex: ^Discord\.Net\.Converters$ | |||
- exclude: | |||
uidRegex: ^Discord\.Net.*$ | |||
uidRegex: ^Discord\.Net.*$ | |||
- exclude: | |||
uidRegex: ^RegexAnalyzer$ |
@@ -1,5 +1,9 @@ | |||
# The Command Service | |||
>[!WARNING] | |||
>This article is out of date, and has not been rewritten yet. | |||
Information is not guaranteed to be accurate. | |||
[Discord.Commands](xref:Discord.Commands) provides an Attribute-based | |||
Command Parser. | |||
@@ -41,7 +45,7 @@ Discord.Net's implementation of Modules is influenced heavily from | |||
ASP.Net Core's Controller pattern. This means that the lifetime of a | |||
module instance is only as long as the command being invoked. | |||
**Avoid using long-running code** in your modules whereever possible. | |||
**Avoid using long-running code** in your modules wherever possible. | |||
You should **not** be implementing very much logic into your modules; | |||
outsource to a service for that. | |||
@@ -163,8 +167,8 @@ a dependency map. | |||
Modules are constructed using Dependency Injection. Any parameters | |||
that are placed in the constructor must be injected into an | |||
@Discord.Commands.IDependencyMap. Alternatively, you may accept an | |||
IDependencyMap as an argument and extract services yourself. | |||
@System.IServiceProvider. Alternatively, you may accept an | |||
IServiceProvider as an argument and extract services yourself. | |||
### Module Properties | |||
@@ -201,21 +205,20 @@ you use DI when writing your modules. | |||
### Setup | |||
First, you need to create an @Discord.Commands.IDependencyMap. | |||
The library includes @Discord.Commands.DependencyMap to help with | |||
this, however you may create your own IDependencyMap if you wish. | |||
First, you need to create an @System.IServiceProvider | |||
You may create your own IServiceProvider if you wish. | |||
Next, add the dependencies your modules will use to the map. | |||
Finally, pass the map into the `LoadAssembly` method. | |||
Your modules will automatically be loaded with this dependency map. | |||
[!code-csharp[DependencyMap Setup](samples/dependency_map_setup.cs)] | |||
[!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] | |||
### Usage in Modules | |||
In the constructor of your module, any parameters will be filled in by | |||
the @Discord.Commands.IDependencyMap you pass into `LoadAssembly`. | |||
the @System.IServiceProvider you pass into `LoadAssembly`. | |||
Any publicly settable properties will also be filled in the same manner. | |||
@@ -224,12 +227,12 @@ Any publicly settable properties will also be filled in the same manner. | |||
being injected. | |||
>[!NOTE] | |||
>If you accept `CommandService` or `IDependencyMap` as a parameter in | |||
>If you accept `CommandService` or `IServiceProvider` as a parameter in | |||
your constructor or as an injectable property, these entries will be filled | |||
by the CommandService the module was loaded from, and the DependencyMap passed | |||
by the CommandService the module was loaded from, and the ServiceProvider passed | |||
into it, respectively. | |||
[!code-csharp[DependencyMap in Modules](samples/dependency_module.cs)] | |||
[!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] | |||
# Preconditions | |||
@@ -1,14 +1,16 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using System.Reflection; | |||
using Discord; | |||
using Discord.WebSocket; | |||
using Discord.Commands; | |||
using Microsoft.Extensions.DependencyInjection; | |||
public class Program | |||
{ | |||
private CommandService commands; | |||
private DiscordSocketClient client; | |||
private DependencyMap map; | |||
private IServiceProvider services; | |||
static void Main(string[] args) => new Program().Start().GetAwaiter().GetResult(); | |||
@@ -19,38 +21,40 @@ public class Program | |||
string token = "bot token here"; | |||
map = new DependencyMap(); | |||
services = new ServiceCollection() | |||
.BuildServiceProvider(); | |||
await InstallCommands(); | |||
await client.LoginAsync(TokenType.Bot, token); | |||
await client.ConnectAsync(); | |||
await client.StartAsync(); | |||
await Task.Delay(-1); | |||
} | |||
public async Task InstallCommands() | |||
{ | |||
// Hook the MessageReceived Event into our Command Handler | |||
client.MessageReceived += HandleCommand; | |||
// Discover all of the commands in this assembly and load them. | |||
// Discover all of the commands in this assembly and load them. | |||
await commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||
} | |||
public async Task HandleCommand(SocketMessage messageParam) | |||
{ | |||
{ | |||
// Don't process the command if it was a System Message | |||
var message = messageParam as SocketUserMessage; | |||
if (message == null) return; | |||
// Create a number to track where the prefix ends and the command begins | |||
int argPos = 0; | |||
// Determine if the message is a command, based on if it starts with '!' or a mention prefix | |||
if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(client.CurrentUser, ref argPos))) return; | |||
// Create a number to track where the prefix ends and the command begins | |||
int argPos = 0; | |||
// Determine if the message is a command, based on if it starts with '!' or a mention prefix | |||
if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(client.CurrentUser, ref argPos))) return; | |||
// Create a Command Context | |||
var context = new CommandContext(client, message); | |||
// Execute the command. (result does not indicate a return value, | |||
// rather an object stating if the command executed succesfully) | |||
var result = await commands.ExecuteAsync(context, argPos, map); | |||
// rather an object stating if the command executed successfully) | |||
var result = await commands.ExecuteAsync(context, argPos, service); | |||
if (!result.IsSuccess) | |||
await context.Channel.SendMessageAsync(result.ErrorReason); | |||
} | |||
} | |||
} |
@@ -7,12 +7,11 @@ public class Commands | |||
{ | |||
public async Task Install(DiscordSocketClient client) | |||
{ | |||
// Here, we will inject the Dependency Map with | |||
// Here, we will inject the ServiceProvider with | |||
// all of the services our client will use. | |||
_map.Add(client); | |||
_map.Add(commands); | |||
_map.Add(new NotificationService(_map)); | |||
_map.Add(new DatabaseService(_map)); | |||
_serviceCollection.AddSingleton(client) | |||
_serviceCollection.AddSingleton(new NotificationService()) | |||
_serviceCollection.AddSingleton(new DatabaseService()) | |||
// ... | |||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||
} |
@@ -1,13 +1,19 @@ | |||
// (Note: This precondition is obsolete, it is recommended to use the RequireOwnerAttribute that is bundled with Discord.Commands) | |||
using Discord.Commands; | |||
using Discord.WebSocket; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using System; | |||
using System.Threading.Tasks; | |||
// Inherit from PreconditionAttribute | |||
public class RequireOwnerAttribute : PreconditionAttribute | |||
{ | |||
// Override the CheckPermissions method | |||
public async override Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) | |||
public async override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
{ | |||
// Get the ID of the bot's owner | |||
var ownerId = (await map.Get<DiscordSocketClient>().GetApplicationInfoAsync()).Owner.Id; | |||
var ownerId = (await services.GetService<DiscordSocketClient>().GetApplicationInfoAsync()).Owner.Id; | |||
// If this command was executed by that user, return a success | |||
if (context.User.Id == ownerId) | |||
return PreconditionResult.FromSuccess(); |
@@ -0,0 +1,58 @@ | |||
--- | |||
title: Managing Connections | |||
--- | |||
In Discord.Net, once a client has been started, it will automatically | |||
maintain a connection to Discord's gateway, until it is manually | |||
stopped. | |||
### Usage | |||
To start a connection, invoke the `StartAsync` method on a client that | |||
supports a WebSocket connection. | |||
These clients include the [DiscordSocketClient] and | |||
[DiscordRpcClient], as well as Audio clients. | |||
To end a connection, invoke the `StopAsync` method. This will | |||
gracefully close any open WebSocket or UdpSocket connections. | |||
Since the Start/Stop methods only signal to an underlying connection | |||
manager that a connection needs to be started, **they return before a | |||
connection is actually made.** | |||
As a result, you will need to hook into one of the connection-state | |||
based events to have an accurate representation of when a client is | |||
ready for use. | |||
All clients provide a `Connected` and `Disconnected` event, which is | |||
raised respectively when a connection opens or closes. In the case of | |||
the DiscordSocketClient, this does **not** mean that the client is | |||
ready to be used. | |||
A separate event, `Ready`, is provided on DiscordSocketClient, which | |||
is raised only when the client has finished guild stream or guild | |||
sync, and has a complete guild cache. | |||
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | |||
[DiscordRpcClient]: xref:Discord.Rpc.DiscordRpcClient | |||
### Samples | |||
[!code-csharp[Connection Sample](samples/events.cs)] | |||
### Tips | |||
Avoid running long-running code on the gateway! If you deadlock the | |||
gateway (as explained in [events]), the connection manager will be | |||
unable to recover and reconnect. | |||
Assuming the client disconnected because of a fault on Discord's end, | |||
and not a deadlock on your end, we will always attempt to reconnect | |||
and resume a connection. | |||
Don't worry about trying to maintain your own connections, the | |||
connection manager is designed to be bulletproof and never fail - if | |||
your client doesn't manage to reconnect, you've found a bug! | |||
[events]: events.md |
@@ -0,0 +1,68 @@ | |||
--- | |||
title: Entities | |||
--- | |||
>[!NOTE] | |||
This article is written with the Socket variants of entities in mind, | |||
not the general interfaces or Rest/Rpc entities. | |||
Discord.Net provides a versatile entity system for navigating the | |||
Discord API. | |||
### Inheritance | |||
Due to the nature of the Discord API, some entities are designed with | |||
multiple variants, for example, `SocketUser` and `SocketGuildUser`. | |||
All models will contain the most detailed version of an entity | |||
possible, even if the type is less detailed. | |||
For example, in the case of the `MessageReceived` event, a | |||
`SocketMessage` is passed in with a channel property of type | |||
`SocketMessageChannel`. All messages come from channels capable of | |||
messaging, so this is the only variant of a channel that can cover | |||
every single case. | |||
But that doesn't mean a message _can't_ come from a | |||
`SocketTextChannel`, which is a message channel in a guild. To | |||
retrieve information about a guild from a message entity, you will | |||
need to cast its channel object to a `SocketTextChannel`. | |||
### Navigation | |||
All socket entities have navigation properties on them, which allow | |||
you to easily navigate to an entity's parent or children. As explained | |||
above, you will sometimes need to cast to a more detailed version of | |||
an entity to navigate to its parent. | |||
### Accessing Entities | |||
The most basic forms of entities, `SocketGuild`, `SocketUser`, and | |||
`SocketChannel` can be pulled from the DiscordSocketClient's global | |||
cache, and can be retrieved using the respective `GetXXX` method on | |||
DiscordSocketClient. | |||
>[!TIP] | |||
It is **vital** that you use the proper IDs for an entity when using | |||
a GetXXX method. It is recommended that you enable Discord's | |||
_developer mode_ to allow easy access to entity IDs, found in | |||
Settings > Appearance > Advanced | |||
More detailed versions of entities can be pulled from the basic | |||
entities, e.g. `SocketGuild.GetUser`, which returns a | |||
`SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a | |||
`SocketGuildChannel`. Again, you may need to cast these objects to get | |||
a variant of the type that you need. | |||
### Samples | |||
[!code-csharp[Entity Sample](samples/entities.cs)] | |||
### Tips | |||
Avoid using boxing-casts to coerce entities into a variant, use the | |||
`as` keyword, and a null-conditional operator. | |||
This allows you to write safer code, and avoid InvalidCastExceptions. | |||
For example, `(message.Author as SocketGuildUser)?.Nickname`. |
@@ -0,0 +1,84 @@ | |||
--- | |||
title: Working with Events | |||
--- | |||
Events in Discord.Net are consumed in a similar manner to the standard | |||
convention, with the exception that every event must be of the type | |||
`System.Threading.Tasks.Task`, and instead of using EventArgs, the | |||
event's parameters are passed directly into the handler. | |||
This allows for events to be handled in an async context directly, | |||
instead of relying on async void. | |||
### Usage | |||
To receive data from an event, hook into it using C#'s delegate | |||
event pattern. | |||
You may opt either to hook an event to an anonymous function (lambda) | |||
or a named function. | |||
### Safety | |||
All events are designed to be thread-safe, in that events are executed | |||
synchronously off the gateway task, in the same context as the gateway | |||
task. | |||
As a side effect, this makes it possible to deadlock the gateway task, | |||
and kill a connection. As a general rule of thumb, any task that takes | |||
longer than three seconds should **not** be awaited directly in the | |||
context of an event, but should be wrapped in a `Task.Run` or | |||
offloaded to another task. | |||
This also means that you should not await a task that requests data | |||
from Discord's gateway in the same context of an event. Since the | |||
gateway will wait on all invoked event handlers to finish before | |||
processing any additional data from the gateway, this will create | |||
a deadlock that will be impossible to recover from. | |||
Exceptions in commands will be swallowed by the gateway and logged out | |||
through the client's log method. | |||
### Common Patterns | |||
As you may know, events in Discord.Net are only given a signature of | |||
`Func<T1, ..., Task>`. There is no room for predefined argument names, | |||
so you must either consult IntelliSense, or view the API documentation | |||
directly. | |||
That being said, there are a variety of common patterns that allow you | |||
to infer what the parameters in an event mean. | |||
#### Entity, Entity | |||
An event handler with a signature of `Func<Entity, Entity, Task>` | |||
typically means that the first object will be a clone of the entity | |||
_before_ a change was made, and the latter object will be an attached | |||
model of the entity _after_ the change was made. | |||
This pattern is typically only found on `EntityUpdated` events. | |||
#### Cacheable | |||
An event handler with a signature of `Func<Cacheable, Entity, Task>` | |||
means that the `before` state of the entity was not provided by the | |||
API, so it can either be pulled from the client's cache, or | |||
downloaded from the API. | |||
See the documentation for [Cacheable] for more information on this | |||
object. | |||
[Cacheable]: xref:Discord.Cacheable`2 | |||
### Samples | |||
[!code-csharp[Event Sample](samples/events.cs)] | |||
### Tips | |||
Many events relating to a Message entity, e.g. `MessageUpdated` | |||
and `ReactionAdded` rely on the client's message cache, which is | |||
**not** enabled by default. Set the `MessageCacheSize` flag in | |||
[DiscordSocketConfig] to enable it. | |||
[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig |
@@ -0,0 +1,42 @@ | |||
--- | |||
title: Logging | |||
--- | |||
Discord.Net's clients provide a [Log] event that all messages will be | |||
disbatched over. | |||
For more information about events in Discord.Net, see the [Events] | |||
section. | |||
[Log]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log | |||
[Events]: events.md | |||
### Usage | |||
To receive log events, simply hook the discord client's log method | |||
to a Task with a single parameter of type [LogMessage] | |||
It is recommended that you use an established function instead of a | |||
lambda for handling logs, because most [addons] accept a reference | |||
to a logging function to write their own messages. | |||
### Usage in Commands | |||
Discord.Net's [CommandService] also provides a log event, identical | |||
in signature to other log events. | |||
Data logged through this event is typically coupled with a | |||
[CommandException], where information about the command's context | |||
and error can be found and handled. | |||
#### Samples | |||
[!code-csharp[Logging Sample](samples/logging.cs)] | |||
#### Tips | |||
Due to the nature of Discord.Net's event system, all log event | |||
handlers will be executed synchronously on the gateway thread. If your | |||
log output will be dumped to a Web API (e.g. Sentry), you are advised | |||
to wrap your output in a `Task.Run` so the gateway thread does not | |||
become blocked while waiting for logging data to be written. |
@@ -0,0 +1,23 @@ | |||
using Discord; | |||
using Discord.WebSocket; | |||
public class Program | |||
{ | |||
private DiscordSocketClient _client; | |||
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||
public async Task MainAsync() | |||
{ | |||
_client = new DiscordSocketClient(); | |||
await _client.LoginAsync(TokenType.Bot, "bot token"); | |||
await _client.StartAsync(); | |||
Console.WriteLine("Press any key to exit..."); | |||
Console.ReadKey(); | |||
await _client.StopAsync(); | |||
// Wait a little for the client to finish disconnecting before allowing the program to return | |||
await Task.Delay(500); | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
public string GetChannelTopic(ulong id) | |||
{ | |||
var channel = client.GetChannel(81384956881809408) as SocketTextChannel; | |||
if (channel == null) return ""; | |||
return channel.Topic; | |||
} | |||
public string GuildOwner(SocketChannel channel) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (guild == null) return ""; | |||
return Context.Guild.Owner.Username; | |||
} |
@@ -0,0 +1,36 @@ | |||
using Discord; | |||
using Discord.WebSocket; | |||
public class Program | |||
{ | |||
private DiscordSocketClient _client; | |||
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||
public async Task MainAsync() | |||
{ | |||
// When working with events that have Cacheable<IMessage, ulong> parameters, | |||
// you must enable the message cache in your config settings if you plan to | |||
// use the cached message entity. | |||
var _config = new DiscordSocketConfig { MessageCacheSize = 100 }; | |||
_client = new DiscordSocketClient(_config); | |||
await _client.LoginAsync(TokenType.Bot, "bot token"); | |||
await _client.StartAsync(); | |||
_client.MessageUpdated += MessageUpdated; | |||
_client.Ready += () => | |||
{ | |||
Console.WriteLine("Bot is connected!"); | |||
return Task.CompletedTask; | |||
} | |||
await Task.Delay(-1); | |||
} | |||
private async Task MessageUpdated(Cacheable<IMessage, ulong> before, SocketMessage after, ISocketMessageChannel channel) | |||
{ | |||
// If the message was not in the cache, downloading it will result in getting a copy of `after`. | |||
var message = await before.GetOrDownloadAsync(); | |||
Console.WriteLine($"{message} -> {after}"); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
using Discord; | |||
using Discord.WebSocket; | |||
public class Program | |||
{ | |||
private DiscordSocketClient _client; | |||
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||
public async Task MainAsync() | |||
{ | |||
_client = new DiscordSocketClient(new DiscordSocketConfig | |||
{ | |||
LogLevel = LogSeverity.Info | |||
}); | |||
_client.Log += Log; | |||
await _client.LoginAsync(TokenType.Bot, "bot token"); | |||
await _client.StartAsync(); | |||
await Task.Delay(-1); | |||
} | |||
private Task Log(LogMessage message) | |||
{ | |||
Console.WriteLine(message.ToString()); | |||
return Task.CompletedTask; | |||
} | |||
} |
@@ -1,28 +0,0 @@ | |||
--- | |||
title: Events | |||
--- | |||
# Events | |||
Messages from Discord are exposed via events, and follow a pattern of `Func<[event params], Task>`, which allows you to easily create either async or sync event handlers. | |||
To hook into events, you must be using the @Discord.WebSocket.DiscordSocketClient, which provides WebSocket capabilities, necessary for receiving events. | |||
>[!NOTE] | |||
>The gateway will wait for all registered handlers of an event to finish before raising the next event. As a result of this, it is reccomended that if you need to perform any heavy work in an event handler, it is done on its own thread or Task. | |||
**For further documentation of all events**, it is reccomended to look at the [Events Section](xref:Discord.WebSocket.DiscordSocketClient#events) on the API documentation of @Discord.WebSocket.DiscordSocketClient | |||
## Connection State | |||
Connection Events will be raised when the Connection State of your client changes. | |||
[DiscordSocketClient.Connected](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_Connected) and [Disconnected](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_Disconnected) are raised when the Gateway Socket connects or disconnects, respectively. | |||
>[!WARNING] | |||
>You should not use DiscordClient.Connected to run code when your client first connects to Discord. The client has not received and parsed the READY event and guild stream yet, and will have an incomplete or empty cache. | |||
[DiscordSocketClient.Ready](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_Ready) is raised when the `READY` packet is parsed and received from Discord. | |||
>[!NOTE] | |||
>The [DiscordSocketClient.ConnectAsync](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_ConnectAsync_System_Boolean_) method will not return until the READY packet has been processed. By default, it also will not return until the guild stream has finished. This means it is safe to run bot code directly after awaiting the ConnectAsync method. |
@@ -0,0 +1,141 @@ | |||
--- | |||
title: Installing Discord.Net | |||
--- | |||
Discord.Net is distributed through the NuGet package manager, and it is | |||
recommended to use NuGet to get started. | |||
Optionally, you may compile from source and install yourself. | |||
# Supported Platforms | |||
Currently, Discord.Net targets [.NET Standard] 1.3, and offers support for | |||
.NET Standard 1.1. If your application will be targeting .NET Standard 1.1, | |||
please see the [additional steps](#installing-on-net-standard-11). | |||
Since Discord.Net is built on the .NET Standard, it is also recommended to | |||
create applications using [.NET Core], though you are not required to. When | |||
using .NET Framework, it is suggested to target `.NET 4.6.1` or higher. | |||
[.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library | |||
[.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/ | |||
# Installing with NuGet | |||
Release builds of Discord.Net 1.0 will be published to the | |||
[official NuGet feed]. | |||
Development builds of Discord.Net 1.0, as well as [addons](TODO) are published | |||
to our development [MyGet feed]. | |||
Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` | |||
Not sure how to add a direct feed? See how [with Visual Studio] | |||
or [without Visual Studio](#configuring-nuget-without-visual-studio) | |||
[official NuGet feed]: https://nuget.org | |||
[MyGet feed]: https://www.myget.org/feed/Packages/discord-net | |||
[with Visual Studio]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources | |||
## Using Visual Studio | |||
1. Create a solution for your bot | |||
2. In Solution Explorer, find the 'Dependencies' element under your bot's | |||
project | |||
3. Right click on 'Dependencies', and select 'Manage NuGet packages' | |||
 | |||
4. In the 'browse' tab, search for 'Discord.Net' | |||
> [!TIP] | |||
Don't forget to change your package source if you're installing from the | |||
developer feed. | |||
Also make sure to check 'Enable Prereleases' if installing a dev build! | |||
5. Install the 'Discord.Net' package | |||
 | |||
## Using JetBrains Rider | |||
1. Create a new solution for your bot | |||
2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for Solution) | |||
 | |||
3. In the 'Packages' tab, search for 'Discord.Net' | |||
 | |||
> [!TIP] | |||
Make sure to check the 'Prerelease' box if installing a dev build! | |||
4. Install by adding the package to your project | |||
 | |||
## Using Visual Studio Code | |||
1. Create a new project for your bot | |||
2. Add Discord.Net to your .csproj | |||
[!code-xml[Sample .csproj](samples/project.csproj)] | |||
> [!TIP] | |||
Don't forget to add the package source to a [NuGet.Config file](#configuring-nuget-without-visual-studio) if you're installing from the | |||
developer feed. | |||
# Compiling from Source | |||
In order to compile Discord.Net, you require the following: | |||
### Using Visual Studio | |||
- [Visual Studio 2017](https://www.visualstudio.com/) | |||
- [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) | |||
The .NET Core and Docker (Preview) workload is required during Visual Studio | |||
installation. | |||
### Using Command Line | |||
- [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) | |||
# Additional Information | |||
## Installing on .NET Standard 1.1 | |||
For applications targeting a runtime corresponding with .NET Standard 1.1 or 1.2, | |||
the builtin WebSocket and UDP provider will not work. For applications which | |||
utilize a WebSocket connection to Discord (WebSocket or RPC), third-party | |||
provider packages will need to be installed and configured. | |||
First, install the following packages through NuGet, or compile yourself, if | |||
you prefer: | |||
- Discord.Net.Providers.WS4Net | |||
- Discord.Net.Providers.UDPClient | |||
Note that `Discord.Net.Providers.UDPClient` is _only_ required if your bot will | |||
be utilizing voice chat. | |||
Next, you will need to configure your [DiscordSocketClient] to use these custom | |||
providers over the default ones. | |||
To do this, set the `WebSocketProvider` and optionally `UdpSocketProvider` | |||
properties on the [DiscordSocketConfig] that you are passing into your | |||
client. | |||
[!code-csharp[NET Standard 1.1 Example](samples/netstd11.cs)] | |||
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | |||
[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig | |||
## Configuring NuGet without Visual Studio | |||
If you plan on deploying your bot or developing outside of Visual Studio, you | |||
will need to create a local NuGet configuration file for your project. | |||
To do this, create a file named `nuget.config` alongside the root of your | |||
application, where the project solution is located. | |||
Paste the following snippets into this configuration file, adding any additional | |||
feeds as necessary. | |||
[!code-xml[NuGet Configuration](samples/nuget.config)] |
@@ -0,0 +1,227 @@ | |||
--- | |||
title: Getting Started | |||
--- | |||
# Making a Ping-Pong bot | |||
One of the first steps to getting started with the Discord API is to | |||
write a basic ping-pong bot. We will expand on this to create more | |||
diverse commands later, but for now, it is a good starting point. | |||
## Creating a Discord Bot | |||
Before you can begin writing your bot, it is necessary to create a bot | |||
account on Discord. | |||
1. Visit the [Discord Applications Portal] | |||
2. Create a New Application | |||
3. Give the application a name (this will be the bot's initial | |||
username). | |||
4. Create the Application | |||
 | |||
5. In the application review page, click **Create a Bot User** | |||
 | |||
6. Confirm the popup | |||
7. If this bot will be public, check 'Public Bot'. | |||
**Do not tick any other options!** | |||
[Discord Applications Portal]: https://discordapp.com/developers/applications/me | |||
## Adding your bot to a server | |||
Bots **can not** use invite links, they must be explicitly invited | |||
through the OAuth2 flow. | |||
1. Open your bot's application on the [Discord Applications Portal] | |||
2. Retrieve the app's **Client ID**. | |||
 | |||
3. Create an OAuth2 authorization URL | |||
`https://discordapp.com/oauth2/authorize?client_id=<CLIENT ID>&scope=bot` | |||
4. Open the authorization URL in your browser | |||
5. Select a server | |||
>[!NOTE] | |||
Only servers where you have the `MANAGE_SERVER` permission will be | |||
present in this list. | |||
6. Click authorize | |||
 | |||
## Connecting to Discord | |||
If you have not already created a project and installed Discord.Net, | |||
do that now. (see the [Installing](installing.md) section) | |||
### Async | |||
Discord.Net uses .NET's Task-based Asynchronous Pattern ([TAP]) | |||
extensively - nearly every operation is asynchronous. | |||
It is highly recommended that these operations be awaited in a | |||
properly established async context whenever possible. Establishing an | |||
async context can be problematic, but not hard. | |||
To do so, we will be creating an async main in your console | |||
application, and rewriting the static main method to invoke the new | |||
async main. | |||
[!code-csharp[Async Context](samples/intro/async-context.cs)] | |||
As a result of this, your program will now start, and immidiately | |||
jump into an async context. This will allow us later on to create a | |||
connection to Discord, without needing to worry about setting up the | |||
correct async implementation. | |||
>[!TIP] | |||
If your application throws any exceptions within an async context, | |||
they will be thrown all the way back up to the first non-async method. | |||
Since our first non-async method is the program's Main method, this | |||
means that **all** unhandled exceptions will be thrown up there, which | |||
will crash your application. Discord.Net will prevent exceptions in | |||
event handlers from crashing your program, but any exceptions in your | |||
async main **will** cause the application to crash. | |||
### Creating a logging method | |||
Before we create and configure a Discord client, we will add a method | |||
to handle Discord.Net's log events. | |||
To allow agnostic support of as many log providers as possible, we | |||
log information through a Log event, with a proprietary LogMessage | |||
parameter. See the [API Documentation] for this event. | |||
If you are using your own logging framework, this is where you would | |||
invoke it. For the sake of simplicity, we will only be logging to | |||
the Console. | |||
[!code-csharp[Async Context](samples/intro/logging.cs)] | |||
### Creating a Discord Client | |||
Finally, we can create a connection to Discord. Since we are writing | |||
a bot, we will be using a [DiscordSocketClient], along with socket | |||
entities. See the [terminology](terminology.md) if you're unsure of | |||
the differences. | |||
To do so, create an instance of [DiscordSocketClient] in your async | |||
main, passing in a configuration object only if necessary. For most | |||
users, the default will work fine. | |||
Before connecting, we should hook the client's log event to the | |||
log handler that was just created. Events in Discord.Net work | |||
similarly to other events in C#, so hook this event the way that | |||
you typically would. | |||
Next, you will need to 'login to Discord' with the `LoginAsync` method. | |||
You may create a variable to hold your bot's token (this can be found | |||
on your bot's application page on the [Discord Applications Portal]). | |||
 | |||
>[!IMPORTANT] | |||
Your bot's token can be used to gain total access to your bot, so | |||
**do __NOT__ share this token with anyone!** It may behoove you to | |||
store this token in an external file if you plan on distributing the | |||
source code for your bot. | |||
We may now invoke the client's `StartAsync` method, which will | |||
start connection/reconnection logic. It is important to note that | |||
**this method returns as soon as connection logic has been started!** | |||
Any methods that rely on the client's state should go in an event | |||
handler. | |||
>[!NOTE] | |||
Connection logic is incomplete as of the current build. Events will | |||
soon be added to indicate when the client's state is ready for use; | |||
(rewrite this section when possible) | |||
Finally, we will want to block the async main method from returning | |||
until after the application is exited. To do this, we can await an | |||
infinite delay, or any other blocking method, such as reading from | |||
the console. | |||
The following lines can now be added: | |||
[!code-csharp[Create client](samples/intro/client.cs)] | |||
At this point, feel free to start your program and see your bot come | |||
online in Discord. | |||
>[!TIP] | |||
Encountering a `PlatformNotSupportedException` when starting your bot? | |||
This means that you are targeting a platform where .NET's default | |||
WebSocket client is not supported. Refer to the [installing guide] | |||
for how to fix this. | |||
[TAP]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async | |||
[API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log | |||
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | |||
[installing guide]: installing.md#installing-on-net-standard-11 | |||
### Handling a 'ping' | |||
Now that we have learned how to open a connection to Discord, we can | |||
begin handling messages that users are sending. | |||
To start out, our bot will listen for any message where the content | |||
is equal to `!ping`, and respond back with `Pong!`. | |||
Since we want to listen for new messages, the event to hook in to | |||
is [MessageReceived]. | |||
In your program, add a method that matches the signature of the | |||
MessageReceived event - it must be a method (`Func`) that returns the | |||
type `Task`, and takes a single parameter, a [SocketMessage]. Also, | |||
since we will be sending data to Discord in this method, we will flag | |||
it as `async`. | |||
In this method, we will add an `if` block, to determine if the message | |||
content fits the rules of our scenario - recall that it must be equal | |||
to `!ping`. | |||
Inside the branch of this condition, we will want to send a message | |||
back to the channel from which the message came - `Pong!`. To find the | |||
channel, look for the `Channel` property on the message parameter. | |||
Next, we will want to send a message to this channel. Since the | |||
channel object is of type [SocketMessageChannel], we can invoke the | |||
`SendMessageAsync` instance method. For the message content, send back | |||
a string containing 'Pong!'. | |||
You should have now added the following lines: | |||
[!code-csharp[Message](samples/intro/message.cs)] | |||
Now, your first bot is complete. You may continue to add on to this | |||
if you desire, but for any bot that will be carrying out multiple | |||
commands, it is strongly encouraged to use the command framework, as | |||
shown below. | |||
For your reference, you may view the [completed program]. | |||
[MessageReceived]: xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived | |||
[SocketMessage]: xref:Discord.WebSocket.SocketMessage | |||
[SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel | |||
[completed program]: samples/intro/complete.cs | |||
# Building a bot with commands | |||
This section will show you how to write a program that is ready for | |||
[commands](commands/commands.md). Note that this will not be explaining _how_ | |||
to write commands or services, it will only be covering the general | |||
structure. | |||
For reference, view an [annotated example] of this structure. | |||
[annotated example]: samples/intro/structure.cs | |||
It is important to know that the recommended design pattern of bots | |||
should be to separate the program (initialization and command handler), | |||
the modules (handle commands), and the services (persistent storage, | |||
pure functions, data manipulation). | |||
**todo:** diagram of bot structure |
@@ -0,0 +1,15 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace MyBot | |||
{ | |||
public class Program | |||
{ | |||
public static void Main(string[] args) | |||
=> new Program().MainAsync().GetAwaiter().GetResult(); | |||
public async Task MainAsync() | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
// Program.cs | |||
using Discord.WebSocket; | |||
// ... | |||
public async Task MainAsync() | |||
{ | |||
var client = new DiscordSocketClient(); | |||
client.Log += Log; | |||
string token = "abcdefg..."; // Remember to keep this private! | |||
await client.LoginAsync(TokenType.Bot, token); | |||
await client.StartAsync(); | |||
// Block this task until the program is closed. | |||
await Task.Delay(-1); | |||
} |
@@ -0,0 +1,42 @@ | |||
using Discord; | |||
using Discord.WebSocket; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace MyBot | |||
{ | |||
public class Program | |||
{ | |||
public static void Main(string[] args) | |||
=> new Program().MainAsync().GetAwaiter().GetResult(); | |||
public async Task MainAsync() | |||
{ | |||
var client = new DiscordSocketClient(); | |||
client.Log += Log; | |||
client.MessageReceived += MessageReceived; | |||
string token = "abcdefg..."; // Remember to keep this private! | |||
await client.LoginAsync(TokenType.Bot, token); | |||
await client.StartAsync(); | |||
// Block this task until the program is closed. | |||
await Task.Delay(-1); | |||
} | |||
private async Task MessageReceived(SocketMessage message) | |||
{ | |||
if (message.Content == "!ping") | |||
{ | |||
await message.Channel.SendMessageAsync("Pong!"); | |||
} | |||
} | |||
private Task Log(LogMessage msg) | |||
{ | |||
Console.WriteLine(msg.ToString()); | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
using Discord; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace MyBot | |||
{ | |||
public class Program | |||
{ | |||
public static void Main(string[] args) | |||
=> new Program().MainAsync().GetAwaiter().GetResult(); | |||
public async Task MainAsync() | |||
{ | |||
} | |||
private Task Log(LogMessage msg) | |||
{ | |||
Console.WriteLine(msg.ToString()); | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
public async Task MainAsync() | |||
{ | |||
// client.Log ... | |||
client.MessageReceived += MessageReceived; | |||
// ... | |||
} | |||
private async Task MessageReceived(SocketMessage message) | |||
{ | |||
if (message.Content == "!ping") | |||
{ | |||
await message.Channel.SendMessageAsync("Pong!"); | |||
} | |||
} |
@@ -1,6 +1,7 @@ | |||
using System; | |||
using System.Reflection; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Discord; | |||
using Discord.Commands; | |||
using Discord.WebSocket; | |||
@@ -9,8 +10,8 @@ class Program | |||
{ | |||
private readonly DiscordSocketClient _client; | |||
// Keep the CommandService and IDependencyMap around for use with commands. | |||
private readonly IDependencyMap _map = new DependencyMap(); | |||
// Keep the CommandService and IServiceCollection around for use with commands. | |||
private readonly IServiceCollection _map = new ServiceCollection(); | |||
private readonly CommandService _commands = new CommandService(); | |||
// Program entry point | |||
@@ -29,8 +30,8 @@ class Program | |||
LogLevel = LogSeverity.Info, | |||
// If you or another service needs to do anything with messages | |||
// (eg. checking Reactions), you should probably | |||
// set the MessageCacheSize here. | |||
// (eg. checking Reactions, checking the content of edited/deleted messages), | |||
// you must set the MessageCacheSize. You may adjust the number as needed. | |||
//MessageCacheSize = 50, | |||
// If your platform doesn't have native websockets, | |||
@@ -40,7 +41,7 @@ class Program | |||
}); | |||
} | |||
// Create a named logging handler, so it can be re-used by addons | |||
// Example of a logging handler. This can be re-used by addons | |||
// that ask for a Func<LogMessage, Task>. | |||
private static Task Logger(LogMessage message) | |||
{ | |||
@@ -64,6 +65,13 @@ class Program | |||
} | |||
Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message}"); | |||
Console.ForegroundColor = cc; | |||
// If you get an error saying 'CompletedTask' doesn't exist, | |||
// your project is targeting .NET 4.5.2 or lower. You'll need | |||
// to adjust your project's target framework to 4.6 or higher | |||
// (instructions for this are easily Googled). | |||
// If you *need* to run on .NET 4.5 for compat/other reasons, | |||
// the alternative is to 'return Task.Delay(0);' instead. | |||
return Task.CompletedTask; | |||
} | |||
@@ -77,28 +85,36 @@ class Program | |||
// Login and connect. | |||
await _client.LoginAsync(TokenType.Bot, /* <DON'T HARDCODE YOUR TOKEN> */); | |||
await _client.ConnectAsync(); | |||
await _client.StartAsync(); | |||
// Wait infinitely so your bot actually stays connected. | |||
await Task.Delay(-1); | |||
} | |||
private IServiceProvider _services; | |||
private async Task InitCommands() | |||
{ | |||
// Repeat this for all the service classes | |||
// and other dependencies that your commands might need. | |||
_map.Add(new SomeServiceClass()); | |||
_map.AddSingleton(new SomeServiceClass()); | |||
// When all your required services are in the collection, build the container. | |||
// Tip: There's an overload taking in a 'validateScopes' bool to make sure | |||
// you haven't made any mistakes in your dependency graph. | |||
_services = _map.BuildServiceProvider(); | |||
// Either search the program and add all Module classes that can be found: | |||
// Either search the program and add all Module classes that can be found. | |||
// Module classes *must* be marked 'public' or they will be ignored. | |||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||
// Or add Modules manually if you prefer to be a little more explicit: | |||
await _commands.AddModuleAsync<SomeModule>(); | |||
// Subscribe a handler to see if a message invokes a command. | |||
_client.MessageReceived += CmdHandler; | |||
_client.MessageReceived += HandleCommandAsync; | |||
} | |||
private async Task CmdHandler(SocketMessage arg) | |||
private async Task HandleCommandAsync(SocketMessage arg) | |||
{ | |||
// Bail out if it's a System Message. | |||
var msg = arg as SocketUserMessage; | |||
@@ -110,14 +126,14 @@ class Program | |||
// you want to prefix your commands with. | |||
// Uncomment the second half if you also want | |||
// commands to be invoked by mentioning the bot instead. | |||
if (msg.HasCharPrefix('!', ref pos) /* || msg.HasMentionPrefix(msg.Discord.CurrentUser, ref pos) */) | |||
if (msg.HasCharPrefix('!', ref pos) /* || msg.HasMentionPrefix(_client.CurrentUser, ref pos) */) | |||
{ | |||
// Create a Command Context | |||
var context = new SocketCommandContext(msg.Discord, msg); | |||
// Create a Command Context. | |||
var context = new SocketCommandContext(_client, msg); | |||
// Execute the command. (result does not indicate a return value, | |||
// rather an object stating if the command executed succesfully). | |||
var result = await _commands.ExecuteAsync(context, pos, _map); | |||
var result = await _commands.ExecuteAsync(context, pos, _services); | |||
// Uncomment the following lines if you want the bot | |||
// to send a message if it failed (not advised for most situations). | |||
@@ -125,4 +141,4 @@ class Program | |||
// await msg.Channel.SendMessageAsync(result.ErrorReason); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
using Discord.Providers.WS4Net; | |||
using Discord.Providers.UDPClient; | |||
using Discord.WebSocket; | |||
// ... | |||
var client = new DiscordSocketClient(new DiscordSocketConfig | |||
{ | |||
WebSocketProvider = WS4NetProvider.Instance, | |||
UdpSocketProvider = UDPClientProvider.Instance, | |||
}); |
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<configuration> | |||
<packageSources> | |||
<add key="discord.net ci feed" value="https://www.myget.org/F/discord-net/api/v3/index.json" /> | |||
</packageSources> | |||
</configuration> |
@@ -0,0 +1,13 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<OutputType>Exe</OutputType> | |||
<TargetFramework>netcoreapp1.1</TargetFramework> | |||
<NoWin32Manifest>true</NoWin32Manifest> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Discord.Net" Version="1.0.0-rc-00617" /> | |||
</ItemGroup> | |||
</Project> |
@@ -1,50 +0,0 @@ | |||
--- | |||
title: Getting Started | |||
--- | |||
# Getting Started | |||
## Requirements | |||
Discord.Net supports logging in with all variations of Discord Accounts, however the Discord API reccomends using a `Bot Account`. | |||
You may [register a bot account here](https://discordapp.com/developers/applications/me). | |||
Bot accounts must be added to a server, you must use the [OAuth 2 Flow](https://discordapp.com/developers/docs/topics/oauth2#adding-bots-to-guilds) to add them to servers. | |||
## Installation | |||
You can install Discord.Net 1.0 from our [MyGet Feed](https://www.myget.org/feed/Packages/discord-net). | |||
**For most users writing bots, install only `Discord.Net.WebSocket`.** | |||
You may add the MyGet feed to Visual Studio directly from `https://www.myget.org/F/discord-net/api/v3/index.json`. | |||
You can also pull the latest source from [GitHub](https://github.com/RogueException/Discord.Net). | |||
>[!WARNING] | |||
>The versions of Discord.Net on NuGet are behind the versions this | |||
>documentation is written for. | |||
>You MUST install from MyGet or Source! | |||
## Async | |||
Discord.Net uses C# tasks extensiely - nearly all operations return | |||
one. | |||
It is highly reccomended these tasks be awaited whenever possible. | |||
To do so requires the calling method to be marked as async, which | |||
can be problematic in a console application. An example of how to | |||
get around this is provided below. | |||
For more information, go to [MSDN's Async-Await section.](https://msdn.microsoft.com/en-us/library/hh191443.aspx) | |||
## First Steps | |||
[!code-csharp[Main](samples/first-steps.cs)] | |||
>[!NOTE] | |||
>In previous versions of Discord.Net, you had to hook into the `Ready` and `GuildAvailable` events to determine when your client was ready for use. | |||
>In 1.0, the [ConnectAsync] method will automatically wait for the Ready event, and for all guilds to stream. To avoid this, pass `false` into `ConnectAsync`. | |||
[ConnectAsync]: xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_ConnectAsync_System_Boolean_ |
@@ -1,11 +0,0 @@ | |||
# Using the Logger | |||
Discord.Net will automatically output log messages through the [Log](xref:Discord.DiscordClient#Discord_DiscordClient_Log) event. | |||
## Usage | |||
To handle Log Messages through Discord.Net's Logger, hook into the [Log](xref:Discord.DiscordClient#Discord_DiscordClient_Log) event. | |||
The @Discord.LogMessage object has a custom `ToString` method attached to it, when outputting log messages, it is reccomended you use this, instead of building your own output message. | |||
[!code-csharp[](samples/logging.cs)] |
@@ -42,7 +42,7 @@ events are delegates, but are still registered the same. | |||
For example, let's look at [DiscordSocketClient.MessageReceived](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived) | |||
To hook an event into MessageReceived, we now use the following code: | |||
[!code-csharp[Event Registration](guides/samples/migrating/event.cs)] | |||
[!code-csharp[Event Registration](samples/event.cs)] | |||
> **All Event Handlers in 1.0 MUST return Task!** | |||
@@ -50,7 +50,7 @@ If your event handler is marked as `async`, it will automatically return `Task`. | |||
if you do not need to execute asynchronus code, do _not_ mark your handler as `async`, and instead, | |||
stick a `return Task.CompletedTask` at the bottom. | |||
[!code-csharp[Sync Event Registration](guides/samples/migrating/sync_event.cs)] | |||
[!code-csharp[Sync Event Registration](samples/sync_event.cs)] | |||
**Event handlers no longer require a sender.** The only arguments your event handler needs to accept | |||
are the parameters used by the event. It is recommended to look at the event in IntelliSense or on the |
@@ -1,20 +0,0 @@ | |||
--- | |||
title: Samples | |||
--- | |||
# Samples | |||
>[!NOTE] | |||
>All of these samples assume you have `_client` defined as a `DiscordSocketClient`. | |||
#### Changing the bot's avatar | |||
[!code-csharp[Bot Avatar](samples/faq/avatar.cs)] | |||
#### Changing the bot's status | |||
[!code-csharp[Bot Status](samples/faq/status.cs)] | |||
#### Sending a message to a channel | |||
[!code-csharp[Message to Channel](samples/faq/send_message.cs)] |
@@ -1,5 +0,0 @@ | |||
public async Task ChangeAvatar() | |||
{ | |||
var fileStream = new FileStream("./newAvatar.png", FileMode.Open); | |||
await _client.CurrentUser.ModifyAsync(x => x.Avatar = fileStream); | |||
} |
@@ -1,6 +0,0 @@ | |||
public async Task SendMessageToChannel(ulong ChannelId) | |||
{ | |||
var channel = _client.GetChannel(ChannelId) as SocketMessageChannel; | |||
await channel?.SendMessageAsync("aaaaaaaaahhh!!!") | |||
/* ^ This question mark is used to indicate that 'channel' may sometimes be null, and in cases that it is null, we will do nothing here. */ | |||
} |
@@ -1,5 +0,0 @@ | |||
public async Task ModifyStatus() | |||
{ | |||
await _client.SetStatusAsync(UserStatus.Idle); | |||
await _client.SetGameAsync("Type !help for help"); | |||
} |
@@ -1,28 +0,0 @@ | |||
using Discord; | |||
using Discord.Rest; | |||
public class Program | |||
{ | |||
private DiscordSocketClient _client; | |||
static void Main(string[] args) => new Program().Start().GetAwaiter().GetResult(); | |||
public async Task Start() | |||
{ | |||
_client = new DiscordSocketClient(new DiscordSocketConfig() { | |||
LogLevel = LogSeverity.Info | |||
}); | |||
_client.Log += Log; | |||
await _client.LoginAsync(TokenType.Bot, "bot token"); | |||
await _client.ConnectAsync(); | |||
await Task.Delay(-1); | |||
} | |||
private Task Log(LogMessage message) | |||
{ | |||
Console.WriteLine(message.ToString()); | |||
return Task.CompletedTask; | |||
} | |||
} |
@@ -1,15 +1,27 @@ | |||
- name: Getting Started | |||
href: intro.md | |||
- name: Terminology | |||
href: terminology.md | |||
- name: Logging | |||
href: logging.md | |||
- name: Commands | |||
href: commands.md | |||
items: | |||
- name: Installation | |||
href: getting_started/installing.md | |||
- name: Your First Bot | |||
href: getting_started/intro.md | |||
- name: Terminology | |||
href: getting_started/terminology.md | |||
- name: Basic Concepts | |||
items: | |||
- name: Logging Data | |||
href: concepts/logging.md | |||
- name: Working with Events | |||
href: concepts/events.md | |||
- name: Managing Connections | |||
href: concepts/connections.md | |||
- name: Entities | |||
href: concepts/entities.md | |||
- name: The Command Service | |||
items: | |||
- name: Command Guide | |||
href: commands/commands.md | |||
- name: Voice | |||
href: voice.md | |||
- name: Events | |||
href: events.md | |||
- name: Code Samples | |||
href: samples.md | |||
items: | |||
- name: Voice Guide | |||
href: voice/sending-voice.md | |||
- name: Migrating from 0.9 |
@@ -3,7 +3,7 @@ private async Task SendAsync(IAudioClient client, string path) | |||
// Create FFmpeg using the previous example | |||
var ffmpeg = CreateStream(path); | |||
var output = ffmpeg.StandardOutput.BaseStream; | |||
var discord = client.CreatePCMStream(1920); | |||
var discord = client.CreatePCMStream(AudioApplication.Mixed); | |||
await output.CopyToAsync(discord); | |||
await discord.FlushAsync(); | |||
} | |||
} |
@@ -1,24 +1,14 @@ | |||
# Voice | |||
--- | |||
title: Sending Voice | |||
--- | |||
**Information on this page is subject to change!** | |||
>[!WARNING] | |||
>Audio in 1.0 is incomplete. Most of the below documentation is untested. | |||
>This article is out of date, and has not been rewritten yet. | |||
Information is not guaranteed to be accurate. | |||
## Installation | |||
To use Audio, you must first configure your [DiscordSocketClient] | |||
with Audio support. | |||
In your [DiscordSocketConfig], set `AudioMode` to the appropriate | |||
[AudioMode] for your bot. For most bots, you will only need to use | |||
`AudioMode.Outgoing`. | |||
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | |||
[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig | |||
[AudioMode]: xref:Discord.Audio.AudioMode | |||
### Dependencies | |||
## Installing | |||
Audio requires two native libraries, `libsodium` and `opus`. | |||
Both of these libraries must be placed in the runtime directory of your |
@@ -3,7 +3,7 @@ | |||
Discord.Net is an asynchronous, multiplatform .NET Library used to interface with the [Discord API](https://discordapp.com/). | |||
If this is your first time using Discord.Net, you should refer to the [Intro](guides/intro.md) for tutorials. | |||
If this is your first time using Discord.Net, you should refer to the [Intro](guides/getting_started/intro.md) for tutorials. | |||
More experienced users might refer to the [API Documentation](api/index.md) for a breakdown of the individuals objects in the library. | |||
For additional resources: | |||
@@ -1,8 +1,6 @@ | |||
- name: Guides | |||
href: guides/ | |||
- name: Migrating | |||
href: migrating.md | |||
- name: API Documentation | |||
href: api/ | |||
homepage: api/index.md |
@@ -0,0 +1,18 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<Import Project="../../Discord.Net.targets" /> | |||
<PropertyGroup> | |||
<AssemblyName>Discord.Net.Analyzers</AssemblyName> | |||
<RootNamespace>Discord.Analyzers</RootNamespace> | |||
<Description>A Discord.Net extension adding compile-time analysis.</Description> | |||
<TargetFrameworks>netstandard1.3</TargetFrameworks> | |||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win81</PackageTargetFallback> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.CodeAnalysis" Version="2.1.0"> | |||
<PrivateAssets>all</PrivateAssets> | |||
</PackageReference> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,18 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<Import Project="../../Discord.Net.targets" /> | |||
<PropertyGroup> | |||
<AssemblyName>Discord.Net.Relay</AssemblyName> | |||
<RootNamespace>Discord.Relay</RootNamespace> | |||
<Description>A core Discord.Net library containing the Relay server.</Description> | |||
<TargetFrameworks>netstandard1.3</TargetFrameworks> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" /> | |||
<ProjectReference Include="..\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" /> | |||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="1.0.2" /> | |||
</ItemGroup> | |||
</Project> |
@@ -1,17 +0,0 @@ | |||
dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } | |||
dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } | |||
dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } | |||
dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } | |||
dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } | |||
dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } | |||
dotnet pack "src\Discord.Net.Providers.UdpClient\Discord.Net.Providers.UdpClient.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } | |||
nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties build="$Env:BUILD" | |||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } |
@@ -1,32 +0,0 @@ | |||
<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VersionPrefix>1.0.0</VersionPrefix> | |||
<VersionSuffix Condition="'$(BuildNumber)' == ''">rc-dev</VersionSuffix> | |||
<VersionSuffix Condition="'$(BuildNumber)' != ''">rc-$(BuildNumber)</VersionSuffix> | |||
<TargetFrameworks>netstandard1.3</TargetFrameworks> | |||
<AssemblyName>Discord.Net.Analyzers</AssemblyName> | |||
<Authors>RogueException</Authors> | |||
<Description>A Discord.Net extension adding compile-time analysis.</Description> | |||
<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> | |||
<RootNamespace>Discord.Analyzers</RootNamespace> | |||
<PackageTargetFallback>portable-net45+win81</PackageTargetFallback> | |||
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.CodeAnalysis" Version="2.0.0-rc2"> | |||
<PrivateAssets>all</PrivateAssets> | |||
</PackageReference> | |||
</ItemGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
</Project> |
@@ -1,11 +1,12 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands | |||
{ | |||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] | |||
public abstract class ParameterPreconditionAttribute : Attribute | |||
{ | |||
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, ParameterInfo parameter, object value, IDependencyMap map); | |||
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services); | |||
} | |||
} |
@@ -6,6 +6,13 @@ namespace Discord.Commands | |||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | |||
public abstract class PreconditionAttribute : Attribute | |||
{ | |||
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map); | |||
/// <summary> | |||
/// Specify a group that this precondition belongs to. Preconditions of the same group require only one | |||
/// of the preconditions to pass in order to be successful (A || B). Specifying <see cref="Group"/> = <see cref="null"/> | |||
/// or not at all will require *all* preconditions to pass, just like normal (A && B). | |||
/// </summary> | |||
public string Group { get; set; } = null; | |||
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services); | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands | |||
{ | |||
@@ -41,16 +42,18 @@ namespace Discord.Commands | |||
GuildPermission = null; | |||
} | |||
public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) | |||
public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
{ | |||
var guildUser = await context.Guild.GetCurrentUserAsync(); | |||
IGuildUser guildUser = null; | |||
if (context.Guild != null) | |||
guildUser = await context.Guild.GetCurrentUserAsync().ConfigureAwait(false); | |||
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}"); | |||
return PreconditionResult.FromError($"Bot requires guild permission {GuildPermission.Value}"); | |||
} | |||
if (ChannelPermission.HasValue) | |||
@@ -64,7 +67,7 @@ namespace Discord.Commands | |||
perms = ChannelPermissions.All(guildChannel); | |||
if (!perms.Has(ChannelPermission.Value)) | |||
return PreconditionResult.FromError($"Command requires channel permission {ChannelPermission.Value}"); | |||
return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}"); | |||
} | |||
return PreconditionResult.FromSuccess(); | |||
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands | |||
{ | |||
@@ -37,7 +38,7 @@ namespace Discord.Commands | |||
Contexts = contexts; | |||
} | |||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) | |||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
{ | |||
bool isValid = false; | |||
@@ -0,0 +1,20 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Discord.Commands | |||
{ | |||
/// <summary> | |||
/// Require that the command is invoked in a channel marked NSFW | |||
/// </summary> | |||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||
public class RequireNsfwAttribute : PreconditionAttribute | |||
{ | |||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
{ | |||
if (context.Channel.IsNsfw) | |||
return Task.FromResult(PreconditionResult.FromSuccess()); | |||
else | |||
return Task.FromResult(PreconditionResult.FromError("This command may only be invoked in an NSFW channel.")); | |||
} | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands | |||
{ | |||
@@ -10,11 +11,22 @@ namespace Discord.Commands | |||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||
public class RequireOwnerAttribute : PreconditionAttribute | |||
{ | |||
public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) | |||
public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
{ | |||
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"); | |||
switch (context.Client.TokenType) | |||
{ | |||
case TokenType.Bot: | |||
var application = await context.Client.GetApplicationInfoAsync(); | |||
if (context.User.Id != application.Owner.Id) | |||
return PreconditionResult.FromError("Command can only be run by the owner of the bot"); | |||
return PreconditionResult.FromSuccess(); | |||
case TokenType.User: | |||
if (context.User.Id != context.Client.CurrentUser.Id) | |||
return PreconditionResult.FromError("Command can only be run by the owner of the bot"); | |||
return PreconditionResult.FromSuccess(); | |||
default: | |||
return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}."); | |||
} | |||
} | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands | |||
{ | |||
@@ -42,7 +43,7 @@ namespace Discord.Commands | |||
GuildPermission = null; | |||
} | |||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) | |||
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
{ | |||
var guildUser = context.User as IGuildUser; | |||
@@ -51,7 +52,7 @@ namespace Discord.Commands | |||
if (guildUser == null) | |||
return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel")); | |||
if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) | |||
return Task.FromResult(PreconditionResult.FromError($"Command requires guild permission {GuildPermission.Value}")); | |||
return Task.FromResult(PreconditionResult.FromError($"User requires guild permission {GuildPermission.Value}")); | |||
} | |||
if (ChannelPermission.HasValue) | |||
@@ -65,7 +66,7 @@ namespace Discord.Commands | |||
perms = ChannelPermissions.All(guildChannel); | |||
if (!perms.Has(ChannelPermission.Value)) | |||
return Task.FromResult(PreconditionResult.FromError($"Command requires channel permission {ChannelPermission.Value}")); | |||
return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}")); | |||
} | |||
return Task.FromResult(PreconditionResult.FromSuccess()); | |||
@@ -2,6 +2,7 @@ | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using System.Collections.Generic; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands.Builders | |||
{ | |||
@@ -9,19 +10,22 @@ namespace Discord.Commands.Builders | |||
{ | |||
private readonly List<PreconditionAttribute> _preconditions; | |||
private readonly List<ParameterBuilder> _parameters; | |||
private readonly List<Attribute> _attributes; | |||
private readonly List<string> _aliases; | |||
public ModuleBuilder Module { get; } | |||
internal Func<ICommandContext, object[], IDependencyMap, Task> Callback { get; set; } | |||
internal Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> Callback { get; set; } | |||
public string Name { get; set; } | |||
public string Summary { get; set; } | |||
public string Remarks { get; set; } | |||
public string PrimaryAlias { get; set; } | |||
public RunMode RunMode { get; set; } | |||
public int Priority { get; set; } | |||
public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions; | |||
public IReadOnlyList<ParameterBuilder> Parameters => _parameters; | |||
public IReadOnlyList<Attribute> Attributes => _attributes; | |||
public IReadOnlyList<string> Aliases => _aliases; | |||
//Automatic | |||
@@ -31,16 +35,18 @@ namespace Discord.Commands.Builders | |||
_preconditions = new List<PreconditionAttribute>(); | |||
_parameters = new List<ParameterBuilder>(); | |||
_attributes = new List<Attribute>(); | |||
_aliases = new List<string>(); | |||
} | |||
//User-defined | |||
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IDependencyMap, Task> callback) | |||
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> callback) | |||
: this(module) | |||
{ | |||
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); | |||
Discord.Preconditions.NotNull(callback, nameof(callback)); | |||
Callback = callback; | |||
PrimaryAlias = primaryAlias; | |||
_aliases.Add(primaryAlias); | |||
} | |||
@@ -74,12 +80,17 @@ namespace Discord.Commands.Builders | |||
{ | |||
for (int i = 0; i < aliases.Length; i++) | |||
{ | |||
var alias = aliases[i] ?? ""; | |||
string alias = aliases[i] ?? ""; | |||
if (!_aliases.Contains(alias)) | |||
_aliases.Add(alias); | |||
} | |||
return this; | |||
} | |||
public CommandBuilder AddAttributes(params Attribute[] attributes) | |||
{ | |||
_attributes.AddRange(attributes); | |||
return this; | |||
} | |||
public CommandBuilder AddPrecondition(PreconditionAttribute precondition) | |||
{ | |||
_preconditions.Add(precondition); | |||
@@ -109,9 +120,9 @@ namespace Discord.Commands.Builders | |||
internal CommandInfo Build(ModuleInfo info, CommandService service) | |||
{ | |||
//Default name to first alias | |||
//Default name to primary alias | |||
if (Name == null) | |||
Name = _aliases[0]; | |||
Name = PrimaryAlias; | |||
if (_parameters.Count > 0) | |||
{ | |||
@@ -119,11 +130,11 @@ namespace Discord.Commands.Builders | |||
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."); | |||
throw new InvalidOperationException($"Only the last parameter in a command may have the Multiple flag. Parameter: {firstMultipleParam.Name} in {PrimaryAlias}"); | |||
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."); | |||
throw new InvalidOperationException($"Only the last parameter in a command may have the Remainder flag. Parameter: {firstRemainderParam.Name} in {PrimaryAlias}"); | |||
} | |||
return new CommandInfo(this, info, service); | |||
@@ -1,6 +1,7 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands.Builders | |||
{ | |||
@@ -9,6 +10,7 @@ namespace Discord.Commands.Builders | |||
private readonly List<CommandBuilder> _commands; | |||
private readonly List<ModuleBuilder> _submodules; | |||
private readonly List<PreconditionAttribute> _preconditions; | |||
private readonly List<Attribute> _attributes; | |||
private readonly List<string> _aliases; | |||
public CommandService Service { get; } | |||
@@ -20,6 +22,7 @@ namespace Discord.Commands.Builders | |||
public IReadOnlyList<CommandBuilder> Commands => _commands; | |||
public IReadOnlyList<ModuleBuilder> Modules => _submodules; | |||
public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions; | |||
public IReadOnlyList<Attribute> Attributes => _attributes; | |||
public IReadOnlyList<string> Aliases => _aliases; | |||
//Automatic | |||
@@ -31,6 +34,7 @@ namespace Discord.Commands.Builders | |||
_commands = new List<CommandBuilder>(); | |||
_submodules = new List<ModuleBuilder>(); | |||
_preconditions = new List<PreconditionAttribute>(); | |||
_attributes = new List<Attribute>(); | |||
_aliases = new List<string>(); | |||
} | |||
//User-defined | |||
@@ -62,18 +66,23 @@ namespace Discord.Commands.Builders | |||
{ | |||
for (int i = 0; i < aliases.Length; i++) | |||
{ | |||
var alias = aliases[i] ?? ""; | |||
string alias = aliases[i] ?? ""; | |||
if (!_aliases.Contains(alias)) | |||
_aliases.Add(alias); | |||
} | |||
return this; | |||
} | |||
public ModuleBuilder AddAttributes(params Attribute[] attributes) | |||
{ | |||
_attributes.AddRange(attributes); | |||
return this; | |||
} | |||
public ModuleBuilder AddPrecondition(PreconditionAttribute precondition) | |||
{ | |||
_preconditions.Add(precondition); | |||
return this; | |||
} | |||
public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IDependencyMap, Task> callback, Action<CommandBuilder> createFunc) | |||
public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> callback, Action<CommandBuilder> createFunc) | |||
{ | |||
var builder = new CommandBuilder(this, primaryAlias, callback); | |||
createFunc(builder); | |||
@@ -12,25 +12,42 @@ namespace Discord.Commands | |||
{ | |||
private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo(); | |||
public static IEnumerable<TypeInfo> Search(Assembly assembly) | |||
public static async Task<IReadOnlyList<TypeInfo>> SearchAsync(Assembly assembly, CommandService service) | |||
{ | |||
foreach (var type in assembly.ExportedTypes) | |||
bool IsLoadableModule(TypeInfo info) | |||
{ | |||
var typeInfo = type.GetTypeInfo(); | |||
if (IsValidModuleDefinition(typeInfo) && | |||
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute))) | |||
return info.DeclaredMethods.Any(x => x.GetCustomAttribute<CommandAttribute>() != null) && | |||
info.GetCustomAttribute<DontAutoLoadAttribute>() == null; | |||
} | |||
var result = new List<TypeInfo>(); | |||
foreach (var typeInfo in assembly.DefinedTypes) | |||
{ | |||
if (typeInfo.IsPublic || typeInfo.IsNestedPublic) | |||
{ | |||
yield return typeInfo; | |||
if (IsValidModuleDefinition(typeInfo) && | |||
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute))) | |||
{ | |||
result.Add(typeInfo); | |||
} | |||
} | |||
else if (IsLoadableModule(typeInfo)) | |||
{ | |||
await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}."); | |||
} | |||
} | |||
return result; | |||
} | |||
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) | |||
public static Task<Dictionary<Type, ModuleInfo>> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service); | |||
public static async Task<Dictionary<Type, ModuleInfo>> BuildAsync(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); | |||
@@ -48,10 +65,13 @@ namespace Discord.Commands | |||
BuildModule(module, typeInfo, service); | |||
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); | |||
builtTypes.Add(typeInfo); | |||
result[typeInfo.AsType()] = module.Build(service); | |||
} | |||
await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.").ConfigureAwait(false); | |||
return result; | |||
} | |||
@@ -81,23 +101,31 @@ namespace Discord.Commands | |||
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.AddAliases((attribute as AliasAttribute).Aliases); | |||
else if (attribute is GroupAttribute) | |||
switch (attribute) | |||
{ | |||
var groupAttr = attribute as GroupAttribute; | |||
builder.Name = builder.Name ?? groupAttr.Prefix; | |||
builder.AddAliases(groupAttr.Prefix); | |||
case NameAttribute name: | |||
builder.Name = name.Text; | |||
break; | |||
case SummaryAttribute summary: | |||
builder.Summary = summary.Text; | |||
break; | |||
case RemarksAttribute remarks: | |||
builder.Remarks = remarks.Text; | |||
break; | |||
case AliasAttribute alias: | |||
builder.AddAliases(alias.Aliases); | |||
break; | |||
case GroupAttribute group: | |||
builder.Name = builder.Name ?? group.Prefix; | |||
builder.AddAliases(group.Prefix); | |||
break; | |||
case PreconditionAttribute precondition: | |||
builder.AddPrecondition(precondition); | |||
break; | |||
default: | |||
builder.AddAttributes(attribute); | |||
break; | |||
} | |||
else if (attribute is PreconditionAttribute) | |||
builder.AddPrecondition(attribute as PreconditionAttribute); | |||
} | |||
//Check for unspecified info | |||
@@ -123,26 +151,35 @@ namespace Discord.Commands | |||
foreach (var attribute in attributes) | |||
{ | |||
// TODO: C#7 type switch | |||
if (attribute is CommandAttribute) | |||
switch (attribute) | |||
{ | |||
var cmdAttr = attribute as CommandAttribute; | |||
builder.AddAliases(cmdAttr.Text); | |||
builder.RunMode = cmdAttr.RunMode; | |||
builder.Name = builder.Name ?? cmdAttr.Text; | |||
case CommandAttribute command: | |||
builder.AddAliases(command.Text); | |||
builder.RunMode = command.RunMode; | |||
builder.Name = builder.Name ?? command.Text; | |||
break; | |||
case NameAttribute name: | |||
builder.Name = name.Text; | |||
break; | |||
case PriorityAttribute priority: | |||
builder.Priority = priority.Priority; | |||
break; | |||
case SummaryAttribute summary: | |||
builder.Summary = summary.Text; | |||
break; | |||
case RemarksAttribute remarks: | |||
builder.Remarks = remarks.Text; | |||
break; | |||
case AliasAttribute alias: | |||
builder.AddAliases(alias.Aliases); | |||
break; | |||
case PreconditionAttribute precondition: | |||
builder.AddPrecondition(precondition); | |||
break; | |||
default: | |||
builder.AddAttributes(attribute); | |||
break; | |||
} | |||
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) | |||
@@ -160,21 +197,34 @@ namespace Discord.Commands | |||
var createInstance = ReflectionUtils.CreateBuilder<IModuleBase>(typeInfo, service); | |||
builder.Callback = (ctx, args, map) => | |||
async Task<IResult> ExecuteCallback(ICommandContext context, object[] args, IServiceProvider services, CommandInfo cmd) | |||
{ | |||
var instance = createInstance(map); | |||
instance.SetContext(ctx); | |||
var instance = createInstance(services); | |||
instance.SetContext(context); | |||
try | |||
{ | |||
instance.BeforeExecute(); | |||
return method.Invoke(instance, args) as Task ?? Task.Delay(0); | |||
instance.BeforeExecute(cmd); | |||
var task = method.Invoke(instance, args) as Task ?? Task.Delay(0); | |||
if (task is Task<RuntimeResult> resultTask) | |||
{ | |||
return await resultTask.ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
await task.ConfigureAwait(false); | |||
return ExecuteResult.FromSuccess(); | |||
} | |||
} | |||
finally | |||
{ | |||
instance.AfterExecute(); | |||
instance.AfterExecute(cmd); | |||
(instance as IDisposable)?.Dispose(); | |||
} | |||
}; | |||
} | |||
builder.Callback = ExecuteCallback; | |||
} | |||
private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service) | |||
@@ -189,24 +239,30 @@ namespace Discord.Commands | |||
foreach (var attribute in attributes) | |||
{ | |||
// TODO: C#7 type switch | |||
if (attribute is SummaryAttribute) | |||
builder.Summary = (attribute as SummaryAttribute).Text; | |||
else if (attribute is OverrideTypeReaderAttribute) | |||
builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader); | |||
else if (attribute is ParameterPreconditionAttribute) | |||
builder.AddPrecondition(attribute as ParameterPreconditionAttribute); | |||
else if (attribute is ParamArrayAttribute) | |||
{ | |||
builder.IsMultiple = true; | |||
paramType = paramType.GetElementType(); | |||
} | |||
else if (attribute is RemainderAttribute) | |||
switch (attribute) | |||
{ | |||
if (position != count-1) | |||
throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); | |||
builder.IsRemainder = true; | |||
case SummaryAttribute summary: | |||
builder.Summary = summary.Text; | |||
break; | |||
case OverrideTypeReaderAttribute typeReader: | |||
builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader); | |||
break; | |||
case ParamArrayAttribute _: | |||
builder.IsMultiple = true; | |||
paramType = paramType.GetElementType(); | |||
break; | |||
case ParameterPreconditionAttribute precon: | |||
builder.AddPrecondition(precon); | |||
break; | |||
case RemainderAttribute _: | |||
if (position != count - 1) | |||
throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}"); | |||
builder.IsRemainder = true; | |||
break; | |||
default: | |||
builder.AddAttributes(attribute); | |||
break; | |||
} | |||
} | |||
@@ -237,7 +293,7 @@ namespace Discord.Commands | |||
} | |||
//We dont have a cached type reader, create one | |||
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty); | |||
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, EmptyServiceProvider.Instance); | |||
service.AddTypeReader(paramType, reader); | |||
return reader; | |||
@@ -252,9 +308,9 @@ namespace Discord.Commands | |||
private static bool IsValidCommandDefinition(MethodInfo methodInfo) | |||
{ | |||
return methodInfo.IsDefined(typeof(CommandAttribute)) && | |||
methodInfo.ReturnType == typeof(Task) && | |||
(methodInfo.ReturnType == typeof(Task) || methodInfo.ReturnType == typeof(Task<RuntimeResult>)) && | |||
!methodInfo.IsStatic && | |||
!methodInfo.IsGenericMethod; | |||
} | |||
} | |||
} | |||
} |
@@ -8,7 +8,8 @@ namespace Discord.Commands.Builders | |||
{ | |||
public class ParameterBuilder | |||
{ | |||
private readonly List<ParameterPreconditionAttribute> _preconditions; | |||
private readonly List<ParameterPreconditionAttribute> _preconditions; | |||
private readonly List<Attribute> _attributes; | |||
public CommandBuilder Command { get; } | |||
public string Name { get; internal set; } | |||
@@ -22,11 +23,13 @@ namespace Discord.Commands.Builders | |||
public string Summary { get; set; } | |||
public IReadOnlyList<ParameterPreconditionAttribute> Preconditions => _preconditions; | |||
public IReadOnlyList<Attribute> Attributes => _attributes; | |||
//Automatic | |||
internal ParameterBuilder(CommandBuilder command) | |||
{ | |||
_preconditions = new List<ParameterPreconditionAttribute>(); | |||
_attributes = new List<Attribute>(); | |||
Command = command; | |||
} | |||
@@ -49,7 +52,7 @@ namespace Discord.Commands.Builders | |||
TypeReader = Command.Module.Service.GetDefaultTypeReader(type); | |||
if (TypeReader == null) | |||
throw new InvalidOperationException($"{type} does not have a TypeReader registered for it"); | |||
throw new InvalidOperationException($"{type} does not have a TypeReader registered for it. Parameter: {Name} in {Command.PrimaryAlias}"); | |||
if (type.GetTypeInfo().IsValueType) | |||
DefaultValue = Activator.CreateInstance(type); | |||
@@ -84,6 +87,11 @@ namespace Discord.Commands.Builders | |||
return this; | |||
} | |||
public ParameterBuilder AddAttributes(params Attribute[] attributes) | |||
{ | |||
_attributes.AddRange(attributes); | |||
return this; | |||
} | |||
public ParameterBuilder AddPrecondition(ParameterPreconditionAttribute precondition) | |||
{ | |||
_preconditions.Add(precondition); | |||
@@ -93,7 +101,7 @@ namespace Discord.Commands.Builders | |||
internal ParameterInfo Build(CommandInfo info) | |||
{ | |||
if (TypeReader == null) | |||
throw new InvalidOperationException($"No default TypeReader found, one must be specified"); | |||
throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified"); | |||
return new ParameterInfo(this, info, Command.Module.Service); | |||
} | |||
@@ -18,6 +18,9 @@ | |||
UnmetPrecondition, | |||
//Execute | |||
Exception | |||
Exception, | |||
//Runtime | |||
Unsuccessful | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
using System; | |||
namespace Discord.Commands | |||
{ | |||
public class CommandException : Exception | |||
{ | |||
public CommandInfo Command { get; } | |||
public ICommandContext Context { get; } | |||
public CommandException(CommandInfo command, ICommandContext context, Exception ex) | |||
: base($"Error occurred executing {command.GetLogText(context)}.", ex) | |||
{ | |||
Command = command; | |||
Context = context; | |||
} | |||
} | |||
} |
@@ -1,5 +1,7 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands | |||
{ | |||
@@ -14,13 +16,13 @@ namespace Discord.Commands | |||
Alias = alias; | |||
} | |||
public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IDependencyMap map = null) | |||
=> Command.CheckPreconditionsAsync(context, map); | |||
public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) | |||
=> Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult); | |||
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IDependencyMap map) | |||
=> Command.ExecuteAsync(context, argList, paramList, map); | |||
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IDependencyMap map) | |||
=> Command.ExecuteAsync(context, parseResult, map); | |||
public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) | |||
=> Command.CheckPreconditionsAsync(context, services); | |||
public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null) | |||
=> Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult, services); | |||
public Task<IResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services) | |||
=> Command.ExecuteAsync(context, argList, paramList, services); | |||
public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) | |||
=> Command.ExecuteAsync(context, parseResult, services); | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System.Collections.Immutable; | |||
using System; | |||
using System.Collections.Immutable; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
@@ -13,7 +14,7 @@ namespace Discord.Commands | |||
QuotedParameter | |||
} | |||
public static async Task<ParseResult> ParseArgs(CommandInfo command, ICommandContext context, string input, int startPos) | |||
public static async Task<ParseResult> ParseArgs(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) | |||
{ | |||
ParameterInfo curParam = null; | |||
StringBuilder argBuilder = new StringBuilder(input.Length); | |||
@@ -110,7 +111,7 @@ namespace Discord.Commands | |||
if (curParam == null) | |||
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters."); | |||
var typeReaderResult = await curParam.Parse(context, argString).ConfigureAwait(false); | |||
var typeReaderResult = await curParam.Parse(context, argString, services).ConfigureAwait(false); | |||
if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches) | |||
return ParseResult.FromError(typeReaderResult); | |||
@@ -133,7 +134,7 @@ namespace Discord.Commands | |||
if (curParam != null && curParam.IsRemainder) | |||
{ | |||
var typeReaderResult = await curParam.Parse(context, argBuilder.ToString()).ConfigureAwait(false); | |||
var typeReaderResult = await curParam.Parse(context, argBuilder.ToString(), services).ConfigureAwait(false); | |||
if (!typeReaderResult.IsSuccess) | |||
return ParseResult.FromError(typeReaderResult); | |||
argList.Add(typeReaderResult); | |||
@@ -1,4 +1,6 @@ | |||
using System; | |||
using Discord.Commands.Builders; | |||
using Discord.Logging; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -6,13 +8,15 @@ using System.Linq; | |||
using System.Reflection; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Discord.Commands.Builders; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Discord.Commands | |||
{ | |||
public class CommandService | |||
{ | |||
public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | |||
internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||
private readonly SemaphoreSlim _moduleLock; | |||
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | |||
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders; | |||
@@ -21,22 +25,29 @@ namespace Discord.Commands | |||
private readonly HashSet<ModuleInfo> _moduleDefs; | |||
private readonly CommandMap _map; | |||
internal readonly bool _caseSensitive; | |||
internal readonly bool _caseSensitive, _throwOnError; | |||
internal readonly char _separatorChar; | |||
internal readonly RunMode _defaultRunMode; | |||
internal readonly Logger _cmdLogger; | |||
internal readonly LogManager _logManager; | |||
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); | |||
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); | |||
public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new {y.Key, y.Value})).ToLookup(x => x.Key, x => x.Value); | |||
public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); | |||
public CommandService() : this(new CommandServiceConfig()) { } | |||
public CommandService(CommandServiceConfig config) | |||
{ | |||
_caseSensitive = config.CaseSensitiveCommands; | |||
_throwOnError = config.ThrowOnError; | |||
_separatorChar = config.SeparatorChar; | |||
_defaultRunMode = config.DefaultRunMode; | |||
if (_defaultRunMode == RunMode.Default) | |||
throw new InvalidOperationException("The default run mode cannot be set to Default, it must be one of Sync, Mixed, or Async"); | |||
throw new InvalidOperationException("The default run mode cannot be set to Default."); | |||
_logManager = new LogManager(config.LogLevel); | |||
_logManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||
_cmdLogger = _logManager.CreateLogger("Command"); | |||
_moduleLock = new SemaphoreSlim(1, 1); | |||
_typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | |||
@@ -48,6 +59,9 @@ namespace Discord.Commands | |||
foreach (var type in PrimitiveParsers.SupportedTypes) | |||
_defaultTypeReaders[type] = PrimitiveTypeReader.Create(type); | |||
_defaultTypeReaders[typeof(string)] = | |||
new PrimitiveTypeReader<string>((string x, out string y) => { y = x; return true; }, 0); | |||
var entityTypeReaders = ImmutableList.CreateBuilder<Tuple<Type, Type>>(); | |||
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IMessage), typeof(MessageTypeReader<>))); | |||
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IChannel), typeof(ChannelTypeReader<>))); | |||
@@ -73,20 +87,21 @@ namespace Discord.Commands | |||
_moduleLock.Release(); | |||
} | |||
} | |||
public async Task<ModuleInfo> AddModuleAsync<T>() | |||
public Task<ModuleInfo> AddModuleAsync<T>() => AddModuleAsync(typeof(T)); | |||
public async Task<ModuleInfo> AddModuleAsync(Type type) | |||
{ | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
var typeInfo = typeof(T).GetTypeInfo(); | |||
var typeInfo = type.GetTypeInfo(); | |||
if (_typedModuleDefs.ContainsKey(typeof(T))) | |||
if (_typedModuleDefs.ContainsKey(type)) | |||
throw new ArgumentException($"This module has already been added."); | |||
var module = ModuleClassBuilder.Build(this, typeInfo).FirstOrDefault(); | |||
var module = (await ModuleClassBuilder.BuildAsync(this, typeInfo).ConfigureAwait(false)).FirstOrDefault(); | |||
if (module.Value == default(ModuleInfo)) | |||
throw new InvalidOperationException($"Could not build the module {typeof(T).FullName}, did you pass an invalid type?"); | |||
throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?"); | |||
_typedModuleDefs[module.Key] = module.Value; | |||
@@ -102,8 +117,8 @@ namespace Discord.Commands | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
var types = ModuleClassBuilder.Search(assembly).ToArray(); | |||
var moduleDefs = ModuleClassBuilder.Build(types, this); | |||
var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false); | |||
var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this).ConfigureAwait(false); | |||
foreach (var info in moduleDefs) | |||
{ | |||
@@ -143,14 +158,13 @@ namespace Discord.Commands | |||
_moduleLock.Release(); | |||
} | |||
} | |||
public async Task<bool> RemoveModuleAsync<T>() | |||
public Task<bool> RemoveModuleAsync<T>() => RemoveModuleAsync(typeof(T)); | |||
public async Task<bool> RemoveModuleAsync(Type type) | |||
{ | |||
await _moduleLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
ModuleInfo module; | |||
_typedModuleDefs.TryGetValue(typeof(T), out module); | |||
if (module == default(ModuleInfo)) | |||
if (!_typedModuleDefs.TryRemove(type, out var module)) | |||
return false; | |||
return RemoveModuleInternal(module); | |||
@@ -184,20 +198,18 @@ namespace Discord.Commands | |||
} | |||
public void AddTypeReader(Type type, TypeReader reader) | |||
{ | |||
var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary<Type, TypeReader>()); | |||
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>()); | |||
readers[reader.GetType()] = reader; | |||
} | |||
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type) | |||
{ | |||
ConcurrentDictionary<Type, TypeReader> definedTypeReaders; | |||
if (_typeReaders.TryGetValue(type, out definedTypeReaders)) | |||
if (_typeReaders.TryGetValue(type, out var definedTypeReaders)) | |||
return definedTypeReaders; | |||
return null; | |||
} | |||
internal TypeReader GetDefaultTypeReader(Type type) | |||
{ | |||
TypeReader reader; | |||
if (_defaultTypeReaders.TryGetValue(type, out reader)) | |||
if (_defaultTypeReaders.TryGetValue(type, out var reader)) | |||
return reader; | |||
var typeInfo = type.GetTypeInfo(); | |||
@@ -223,70 +235,110 @@ namespace Discord.Commands | |||
} | |||
//Execution | |||
public SearchResult Search(ICommandContext context, int argPos) | |||
public SearchResult Search(ICommandContext context, int argPos) | |||
=> Search(context, context.Message.Content.Substring(argPos)); | |||
public SearchResult Search(ICommandContext context, string input) | |||
{ | |||
string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); | |||
var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray(); | |||
if (matches.Length > 0) | |||
return SearchResult.FromSuccess(input, matches); | |||
else | |||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | |||
} | |||
public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); | |||
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling); | |||
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
{ | |||
dependencyMap = dependencyMap ?? DependencyMap.Empty; | |||
services = services ?? EmptyServiceProvider.Instance; | |||
var searchResult = Search(context, input); | |||
if (!searchResult.IsSuccess) | |||
return searchResult; | |||
var commands = searchResult.Commands; | |||
for (int i = commands.Count - 1; i >= 0; i--) | |||
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>(); | |||
foreach (var match in commands) | |||
{ | |||
var preconditionResult = await commands[i].CheckPreconditionsAsync(context, dependencyMap).ConfigureAwait(false); | |||
if (!preconditionResult.IsSuccess) | |||
{ | |||
if (commands.Count == 1) | |||
return preconditionResult; | |||
else | |||
continue; | |||
} | |||
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); | |||
} | |||
var successfulPreconditions = preconditionResults | |||
.Where(x => x.Value.IsSuccess) | |||
.ToArray(); | |||
var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false); | |||
if (!parseResult.IsSuccess) | |||
if (successfulPreconditions.Length == 0) | |||
{ | |||
//All preconditions failed, return the one from the highest priority command | |||
var bestCandidate = preconditionResults | |||
.OrderByDescending(x => x.Key.Command.Priority) | |||
.FirstOrDefault(x => !x.Value.IsSuccess); | |||
return bestCandidate.Value; | |||
} | |||
//If we get this far, at least one precondition was successful. | |||
var parseResultsDict = new Dictionary<CommandMatch, ParseResult>(); | |||
foreach (var pair in successfulPreconditions) | |||
{ | |||
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false); | |||
if (parseResult.Error == CommandError.MultipleMatches) | |||
{ | |||
if (parseResult.Error == CommandError.MultipleMatches) | |||
IReadOnlyList<TypeReaderValue> argList, paramList; | |||
switch (multiMatchHandling) | |||
{ | |||
IReadOnlyList<TypeReaderValue> argList, paramList; | |||
switch (multiMatchHandling) | |||
{ | |||
case MultiMatchHandling.Best: | |||
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); | |||
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); | |||
parseResult = ParseResult.FromSuccess(argList, paramList); | |||
break; | |||
} | |||
case MultiMatchHandling.Best: | |||
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); | |||
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); | |||
parseResult = ParseResult.FromSuccess(argList, paramList); | |||
break; | |||
} | |||
} | |||
if (!parseResult.IsSuccess) | |||
{ | |||
if (commands.Count == 1) | |||
return parseResult; | |||
else | |||
continue; | |||
} | |||
parseResultsDict[pair.Key] = parseResult; | |||
} | |||
// Calculates the 'score' of a command given a parse result | |||
float CalculateScore(CommandMatch match, ParseResult parseResult) | |||
{ | |||
float argValuesScore = 0, paramValuesScore = 0; | |||
if (match.Command.Parameters.Count > 0) | |||
{ | |||
var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; | |||
var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; | |||
argValuesScore = argValuesSum / match.Command.Parameters.Count; | |||
paramValuesScore = paramValuesSum / match.Command.Parameters.Count; | |||
} | |||
return await commands[i].ExecuteAsync(context, parseResult, dependencyMap).ConfigureAwait(false); | |||
var totalArgsScore = (argValuesScore + paramValuesScore) / 2; | |||
return match.Command.Priority + totalArgsScore * 0.99f; | |||
} | |||
//Order the parse results by their score so that we choose the most likely result to execute | |||
var parseResults = parseResultsDict | |||
.OrderByDescending(x => CalculateScore(x.Key, x.Value)); | |||
var successfulParses = parseResults | |||
.Where(x => x.Value.IsSuccess) | |||
.ToArray(); | |||
if (successfulParses.Length == 0) | |||
{ | |||
//All parses failed, return the one from the highest priority command, using score as a tie breaker | |||
var bestMatch = parseResults | |||
.FirstOrDefault(x => !x.Value.IsSuccess); | |||
return bestMatch.Value; | |||
} | |||
return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); | |||
//If we get this far, at least one parse was successful. Execute the most likely overload. | |||
var chosenOverload = successfulParses[0]; | |||
return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false); | |||
} | |||
} | |||
} |
@@ -8,5 +8,11 @@ | |||
public char SeparatorChar { get; set; } = ' '; | |||
/// <summary> Should commands be case-sensitive? </summary> | |||
public bool CaseSensitiveCommands { get; set; } = false; | |||
/// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> | |||
public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | |||
/// <summary> Gets or sets whether RunMode.Sync commands should push exceptions up to the caller. </summary> | |||
public bool ThrowOnError { get; set; } = true; | |||
} | |||
} | |||
} |
@@ -1,98 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Discord.Commands | |||
{ | |||
public class DependencyMap : IDependencyMap | |||
{ | |||
private Dictionary<Type, Func<object>> map; | |||
public static DependencyMap Empty => new DependencyMap(); | |||
public DependencyMap() | |||
{ | |||
map = new Dictionary<Type, Func<object>>(); | |||
} | |||
/// <inheritdoc /> | |||
public void Add<T>(T obj) where T : class | |||
=> AddFactory(() => obj); | |||
/// <inheritdoc /> | |||
public bool TryAdd<T>(T obj) where T : class | |||
=> TryAddFactory(() => obj); | |||
/// <inheritdoc /> | |||
public void AddTransient<T>() where T : class, new() | |||
=> AddFactory(() => new T()); | |||
/// <inheritdoc /> | |||
public bool TryAddTransient<T>() where T : class, new() | |||
=> TryAddFactory(() => new T()); | |||
/// <inheritdoc /> | |||
public void AddTransient<TKey, TImpl>() where TKey : class | |||
where TImpl : class, TKey, new() | |||
=> AddFactory<TKey>(() => new TImpl()); | |||
public bool TryAddTransient<TKey, TImpl>() where TKey : class | |||
where TImpl : class, TKey, new() | |||
=> TryAddFactory<TKey>(() => new TImpl()); | |||
/// <inheritdoc /> | |||
public void AddFactory<T>(Func<T> factory) where T : class | |||
{ | |||
var t = typeof(T); | |||
if (map.ContainsKey(t)) | |||
throw new InvalidOperationException($"The dependency map already contains \"{t.FullName}\""); | |||
map.Add(t, factory); | |||
} | |||
/// <inheritdoc /> | |||
public bool TryAddFactory<T>(Func<T> factory) where T : class | |||
{ | |||
var t = typeof(T); | |||
if (map.ContainsKey(t)) | |||
return false; | |||
map.Add(t, factory); | |||
return true; | |||
} | |||
/// <inheritdoc /> | |||
public T Get<T>() | |||
{ | |||
return (T)Get(typeof(T)); | |||
} | |||
/// <inheritdoc /> | |||
public object Get(Type t) | |||
{ | |||
object result; | |||
if (!TryGet(t, out result)) | |||
throw new KeyNotFoundException($"The dependency map does not contain \"{t.FullName}\""); | |||
else | |||
return result; | |||
} | |||
/// <inheritdoc /> | |||
public bool TryGet<T>(out T result) | |||
{ | |||
object untypedResult; | |||
if (TryGet(typeof(T), out untypedResult)) | |||
{ | |||
result = (T)untypedResult; | |||
return true; | |||
} | |||
else | |||
{ | |||
result = default(T); | |||
return false; | |||
} | |||
} | |||
/// <inheritdoc /> | |||
public bool TryGet(Type t, out object result) | |||
{ | |||
Func<object> func; | |||
if (map.TryGetValue(t, out func)) | |||
{ | |||
result = func(); | |||
return true; | |||
} | |||
result = null; | |||
return false; | |||
} | |||
} | |||
} |
@@ -1,89 +0,0 @@ | |||
using System; | |||
namespace Discord.Commands | |||
{ | |||
public interface IDependencyMap | |||
{ | |||
/// <summary> | |||
/// Add an instance of a service to be injected. | |||
/// </summary> | |||
/// <typeparam name="T">The type of service.</typeparam> | |||
/// <param name="obj">The instance of a service.</param> | |||
void Add<T>(T obj) where T : class; | |||
/// <summary> | |||
/// Tries to add an instance of a service to be injected. | |||
/// </summary> | |||
/// <typeparam name="T">The type of service.</typeparam> | |||
/// <param name="obj">The instance of a service.</param> | |||
/// <returns>A bool, indicating if the service was successfully added to the DependencyMap.</returns> | |||
bool TryAdd<T>(T obj) where T : class; | |||
/// <summary> | |||
/// Add a service that will be injected by a new instance every time. | |||
/// </summary> | |||
/// <typeparam name="T">The type of instance to inject.</typeparam> | |||
void AddTransient<T>() where T : class, new(); | |||
/// <summary> | |||
/// Tries to add a service that will be injected by a new instance every time. | |||
/// </summary> | |||
/// <typeparam name="T">The type of instance to inject.</typeparam> | |||
/// <returns>A bool, indicating if the service was successfully added to the DependencyMap.</returns> | |||
bool TryAddTransient<T>() where T : class, new(); | |||
/// <summary> | |||
/// Add a service that will be injected by a new instance every time. | |||
/// </summary> | |||
/// <typeparam name="TKey">The type to look for when injecting.</typeparam> | |||
/// <typeparam name="TImpl">The type to inject when injecting.</typeparam> | |||
/// <example> | |||
/// map.AddTransient<IService, Service> | |||
/// </example> | |||
void AddTransient<TKey, TImpl>() where TKey: class where TImpl : class, TKey, new(); | |||
/// <summary> | |||
/// Tries to add a service that will be injected by a new instance every time. | |||
/// </summary> | |||
/// <typeparam name="TKey">The type to look for when injecting.</typeparam> | |||
/// <typeparam name="TImpl">The type to inject when injecting.</typeparam> | |||
/// <returns>A bool, indicating if the service was successfully added to the DependencyMap.</returns> | |||
bool TryAddTransient<TKey, TImpl>() where TKey : class where TImpl : class, TKey, new(); | |||
/// <summary> | |||
/// Add a service that will be injected by a factory. | |||
/// </summary> | |||
/// <typeparam name="T">The type to look for when injecting.</typeparam> | |||
/// <param name="factory">The factory that returns a type of this service.</param> | |||
void AddFactory<T>(Func<T> factory) where T : class; | |||
/// <summary> | |||
/// Tries to add a service that will be injected by a factory. | |||
/// </summary> | |||
/// <typeparam name="T">The type to look for when injecting.</typeparam> | |||
/// <param name="factory">The factory that returns a type of this service.</param> | |||
/// <returns>A bool, indicating if the service was successfully added to the DependencyMap.</returns> | |||
bool TryAddFactory<T>(Func<T> factory) where T : class; | |||
/// <summary> | |||
/// Pull an object from the map. | |||
/// </summary> | |||
/// <typeparam name="T">The type of service.</typeparam> | |||
/// <returns>An instance of this service.</returns> | |||
T Get<T>(); | |||
/// <summary> | |||
/// Try to pull an object from the map. | |||
/// </summary> | |||
/// <typeparam name="T">The type of service.</typeparam> | |||
/// <param name="result">The instance of this service.</param> | |||
/// <returns>Whether or not this object could be found in the map.</returns> | |||
bool TryGet<T>(out T result); | |||
/// <summary> | |||
/// Pull an object from the map. | |||
/// </summary> | |||
/// <param name="t">The type of service.</param> | |||
/// <returns>An instance of this service.</returns> | |||
object Get(Type t); | |||
/// <summary> | |||
/// Try to pull an object from the map. | |||
/// </summary> | |||
/// <param name="t">The type of service.</param> | |||
/// <param name="result">An instance of this service.</param> | |||
/// <returns>Whether or not this object could be found in the map.</returns> | |||
bool TryGet(Type t, out object result); | |||
} | |||
} |
@@ -1,26 +1,15 @@ | |||
<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<Import Project="../../Discord.Net.targets" /> | |||
<PropertyGroup> | |||
<VersionPrefix>1.0.0</VersionPrefix> | |||
<VersionSuffix Condition="'$(BuildNumber)' == ''">rc-dev</VersionSuffix> | |||
<VersionSuffix Condition="'$(BuildNumber)' != ''">rc-$(BuildNumber)</VersionSuffix> | |||
<TargetFrameworks>netstandard1.1;netstandard1.3</TargetFrameworks> | |||
<AssemblyName>Discord.Net.Commands</AssemblyName> | |||
<Authors>RogueException</Authors> | |||
<Description>A Discord.Net extension adding support for bot commands.</Description> | |||
<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> | |||
<RootNamespace>Discord.Commands</RootNamespace> | |||
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences> | |||
<Description>A Discord.Net extension adding support for bot commands.</Description> | |||
<TargetFrameworks>netstandard1.1</TargetFrameworks> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
</ItemGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | |||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | |||
<WarningsAsErrors>true</WarningsAsErrors> | |||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,11 @@ | |||
using System; | |||
namespace Discord.Commands | |||
{ | |||
internal class EmptyServiceProvider : IServiceProvider | |||
{ | |||
public static readonly EmptyServiceProvider Instance = new EmptyServiceProvider(); | |||
public object GetService(Type serviceType) => null; | |||
} | |||
} |
@@ -1,4 +1,6 @@ | |||
namespace Discord.Commands | |||
using System; | |||
namespace Discord.Commands | |||
{ | |||
public static class MessageExtensions | |||
{ | |||
@@ -12,10 +14,10 @@ | |||
} | |||
return false; | |||
} | |||
public static bool HasStringPrefix(this IUserMessage msg, string str, ref int argPos) | |||
public static bool HasStringPrefix(this IUserMessage msg, string str, ref int argPos, StringComparison comparisonType = StringComparison.Ordinal) | |||
{ | |||
var text = msg.Content; | |||
if (text.StartsWith(str)) | |||
if (text.StartsWith(str, comparisonType)) | |||
{ | |||
argPos = str.Length; | |||
return true; | |||
@@ -4,8 +4,8 @@ | |||
{ | |||
void SetContext(ICommandContext context); | |||
void BeforeExecute(); | |||
void BeforeExecute(CommandInfo command); | |||
void AfterExecute(); | |||
void AfterExecute(CommandInfo command); | |||
} | |||
} |