Browse Source

Merge branch 'dev' into feature/reactions

tags/1.0-rc
RogueException 8 years ago
parent
commit
b9eeac9105
100 changed files with 2677 additions and 1107 deletions
  1. +75
    -23
      Discord.Net.sln
  2. +9
    -14
      README.md
  3. +13
    -6
      build.bat
  4. +2
    -2
      docs/guides/samples.md
  5. +3
    -6
      docs/guides/samples/faq/status.cs
  6. +0
    -3
      global.json
  7. +1
    -1
      src/Discord.Net.Commands/Attributes/CommandAttribute.cs
  8. +75
    -0
      src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs
  9. +16
    -0
      src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs
  10. +23
    -0
      src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs
  11. +25
    -3
      src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs
  12. +120
    -0
      src/Discord.Net.Commands/Builders/CommandBuilder.cs
  13. +109
    -0
      src/Discord.Net.Commands/Builders/ModuleBuilder.cs
  14. +232
    -0
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  15. +79
    -0
      src/Discord.Net.Commands/Builders/ParameterBuilder.cs
  16. +0
    -284
      src/Discord.Net.Commands/CommandInfo.cs
  17. +5
    -7
      src/Discord.Net.Commands/CommandParser.cs
  18. +85
    -53
      src/Discord.Net.Commands/CommandService.cs
  19. +10
    -0
      src/Discord.Net.Commands/CommandServiceConfig.cs
  20. +41
    -0
      src/Discord.Net.Commands/Discord.Net.Commands.csproj
  21. +0
    -19
      src/Discord.Net.Commands/Discord.Net.Commands.xproj
  22. +22
    -0
      src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs
  23. +194
    -0
      src/Discord.Net.Commands/Info/CommandInfo.cs
  24. +96
    -0
      src/Discord.Net.Commands/Info/ModuleInfo.cs
  25. +24
    -21
      src/Discord.Net.Commands/Info/ParameterInfo.cs
  26. +14
    -70
      src/Discord.Net.Commands/Map/CommandMap.cs
  27. +23
    -4
      src/Discord.Net.Commands/Map/CommandMapNode.cs
  28. +2
    -2
      src/Discord.Net.Commands/ModuleBase.cs
  29. +0
    -85
      src/Discord.Net.Commands/ModuleInfo.cs
  30. +7
    -5
      src/Discord.Net.Commands/Readers/EnumTypeReader.cs
  31. +1
    -0
      src/Discord.Net.Commands/RunMode.cs
  32. +1
    -1
      src/Discord.Net.Commands/Utilities/ReflectionUtils.cs
  33. +2
    -2
      src/Discord.Net.Commands/project.json
  34. +15
    -0
      src/Discord.Net.Core/API/Common/Embed.cs
  35. +16
    -0
      src/Discord.Net.Core/API/Common/EmbedAuthor.cs
  36. +14
    -0
      src/Discord.Net.Core/API/Common/EmbedField.cs
  37. +14
    -0
      src/Discord.Net.Core/API/Common/EmbedFooter.cs
  38. +17
    -0
      src/Discord.Net.Core/API/Common/EmbedImage.cs
  39. +15
    -0
      src/Discord.Net.Core/API/Common/EmbedVideo.cs
  40. +41
    -51
      src/Discord.Net.Core/API/DiscordRestApiClient.cs
  41. +3
    -0
      src/Discord.Net.Core/API/Rest/CreateMessageParams.cs
  42. +2
    -0
      src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs
  43. +60
    -0
      src/Discord.Net.Core/Discord.Net.Core.csproj
  44. +0
    -19
      src/Discord.Net.Core/Discord.Net.Core.xproj
  45. +2
    -1
      src/Discord.Net.Core/DiscordConfig.cs
  46. +1
    -1
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  47. +29
    -0
      src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs
  48. +208
    -0
      src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
  49. +27
    -0
      src/Discord.Net.Core/Entities/Messages/EmbedField.cs
  50. +27
    -0
      src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs
  51. +31
    -0
      src/Discord.Net.Core/Entities/Messages/EmbedImage.cs
  52. +2
    -2
      src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs
  53. +29
    -0
      src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs
  54. +11
    -1
      src/Discord.Net.Core/Entities/Messages/IEmbed.cs
  55. +1
    -2
      src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
  56. +12
    -0
      src/Discord.Net.Core/Entities/Messages/ModifyMessageParams.cs
  57. +7
    -5
      src/Discord.Net.Core/Entities/Messages/TagHandling.cs
  58. +6
    -0
      src/Discord.Net.Core/Entities/Roles/Color.cs
  59. +2
    -2
      src/Discord.Net.Core/Entities/Roles/IRole.cs
  60. +14
    -5
      src/Discord.Net.Core/Extensions/GuildUserExtensions.cs
  61. +18
    -0
      src/Discord.Net.Core/Extensions/RoleExtensions.cs
  62. +31
    -9
      src/Discord.Net.Core/Net/Queue/ClientBucket.cs
  63. +2
    -0
      src/Discord.Net.Core/Net/Queue/RequestQueue.cs
  64. +96
    -44
      src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs
  65. +1
    -1
      src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs
  66. +2
    -0
      src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs
  67. +2
    -2
      src/Discord.Net.Core/RequestOptions.cs
  68. +43
    -11
      src/Discord.Net.Core/Utils/MentionUtils.cs
  69. +158
    -124
      src/Discord.Net.Core/Utils/Preconditions.cs
  70. +8
    -8
      src/Discord.Net.Core/project.json
  71. +44
    -0
      src/Discord.Net.Rest/Discord.Net.Rest.csproj
  72. +0
    -19
      src/Discord.Net.Rest/Discord.Net.Rest.xproj
  73. +11
    -2
      src/Discord.Net.Rest/DiscordRestClient.cs
  74. +0
    -1
      src/Discord.Net.Rest/DiscordRestConfig.cs
  75. +55
    -22
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  76. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs
  77. +11
    -11
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  78. +11
    -11
      src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
  79. +11
    -11
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  80. +11
    -11
      src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs
  81. +1
    -1
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  82. +1
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  83. +39
    -4
      src/Discord.Net.Rest/Entities/Messages/Embed.cs
  84. +6
    -1
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  85. +5
    -7
      src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
  86. +4
    -6
      src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs
  87. +11
    -9
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  88. +16
    -5
      src/Discord.Net.Rest/Entities/Roles/RestRole.cs
  89. +4
    -4
      src/Discord.Net.Rest/project.json
  90. +48
    -0
      src/Discord.Net.Rpc/Discord.Net.Rpc.csproj
  91. +0
    -19
      src/Discord.Net.Rpc/Discord.Net.Rpc.xproj
  92. +6
    -3
      src/Discord.Net.Rpc/DiscordRpcClient.cs
  93. +11
    -11
      src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs
  94. +11
    -11
      src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs
  95. +11
    -11
      src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs
  96. +4
    -1
      src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs
  97. +4
    -4
      src/Discord.Net.Rpc/project.json
  98. +58
    -0
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
  99. +0
    -19
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj
  100. +6
    -2
      src/Discord.Net.WebSocket/DiscordSocketClient.cs

+ 75
- 23
Discord.Net.sln View File

@@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
# Visual Studio 15
VisualStudioVersion = 15.0.25914.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@@ -9,57 +9,109 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md README.md = README.md
EndProjectSection EndProjectSection
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net", "src\Discord.Net\Discord.Net.csproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.csproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.csproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj", "{688FD1D8-7F01-4539-B2E9-F473C5D699C7}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.Build.0 = Debug|Any CPU {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Release|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x64.ActiveCfg = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x64.Build.0 = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x86.ActiveCfg = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x86.Build.0 = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x64.ActiveCfg = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x64.Build.0 = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x86.ActiveCfg = Debug|Any CPU
{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x86.Build.0 = Debug|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x64.ActiveCfg = Debug|x64
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x64.Build.0 = Debug|x64
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x86.ActiveCfg = Debug|x86
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x86.Build.0 = Debug|x86
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Debug|Any CPU
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x64.ActiveCfg = Release|x64
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x64.Build.0 = Release|x64
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x86.ActiveCfg = Release|x86
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x86.Build.0 = Release|x86
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Release|Any CPU
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.Build.0 = Release|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x64.ActiveCfg = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x64.Build.0 = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x86.ActiveCfg = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x86.Build.0 = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x64.ActiveCfg = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x64.Build.0 = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x86.ActiveCfg = Debug|Any CPU
{BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x86.Build.0 = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.Build.0 = Release|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x64.ActiveCfg = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x64.Build.0 = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x86.ActiveCfg = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x86.Build.0 = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.Build.0 = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x64.ActiveCfg = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x64.Build.0 = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x86.ActiveCfg = Debug|Any CPU
{5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x86.Build.0 = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Release|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x64.ActiveCfg = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x64.Build.0 = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x86.ActiveCfg = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x86.Build.0 = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x64.ActiveCfg = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x64.Build.0 = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x86.ActiveCfg = Debug|Any CPU
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x86.Build.0 = Debug|Any CPU
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x64.ActiveCfg = Debug|x64
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x64.Build.0 = Debug|x64
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x86.ActiveCfg = Debug|x86
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x86.Build.0 = Debug|x86
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|Any CPU.Build.0 = Release|Any CPU
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x64.ActiveCfg = Release|x64
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x64.Build.0 = Release|x64
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x86.ActiveCfg = Release|x86
{688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x86.Build.0 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{BFC6DC28-0351-4573-926A-D4124244C04F} = {288C363D-A636-4EAE-9AC1-4698B641B26E} {BFC6DC28-0351-4573-926A-D4124244C04F} = {288C363D-A636-4EAE-9AC1-4698B641B26E}
{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D} = {288C363D-A636-4EAE-9AC1-4698B641B26E}
{5688A353-121E-40A1-8BFA-B17B91FB48FB} = {288C363D-A636-4EAE-9AC1-4698B641B26E} {5688A353-121E-40A1-8BFA-B17B91FB48FB} = {288C363D-A636-4EAE-9AC1-4698B641B26E}
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

+ 9
- 14
README.md View File

@@ -1,4 +1,4 @@
# Discord.Net v1.0.0-beta
# Discord.Net v1.0.0-beta2
[![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net) [![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net)
[![MyGet Build Status](https://www.myget.org/BuildSource/Badge/discord-net?identifier=15bf7c42-22dd-4406-93e5-3cafc62bbc85)](https://www.myget.org/) [![MyGet Build Status](https://www.myget.org/BuildSource/Badge/discord-net?identifier=15bf7c42-22dd-4406-93e5-3cafc62bbc85)](https://www.myget.org/)
[![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/0SBTUU1wZTYLhAAW) [![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/0SBTUU1wZTYLhAAW)
@@ -19,20 +19,15 @@ Bleeding edge builds are available using our MyGet feed (`https://www.myget.org/
## Compiling ## Compiling
In order to compile Discord.Net, you require the following: In order to compile Discord.Net, you require the following:


### Using Visual Studio 2015
- [VS2015 Update 3](https://www.microsoft.com/net/core#windows)
- [.Net Core 1.0 VS Plugin](https://www.microsoft.com/net/core#windows)
### Using Visual Studio
- [Visual Studio 2017 RC](https://www.microsoft.com/net/core#windowsvs2017)


### Using CLI
- [.Net Core 1.0 SDK](https://www.microsoft.com/net/core)
The .NET Core and Docker (Preview) workload is required during Visual Studio installation.


## Known Issues

### WebSockets
The current stable .Net Core websocket package does not support Linux, or pre-Win8.
### Using Command Line
- [.Net Core 1.1 SDK](https://www.microsoft.com/net/download/core)


#### Linux
Add the latest version of `System.Net.WebSockets.Client` from the .Net Core MyGet feed (`https://dotnet.myget.org/F/dotnet-core/api/v3/index.json`) to your project.
## Known Issues


#### Windows 7 and earlier
There is currently no workaround, track the issue [here](https://github.com/dotnet/corefx/issues/9503).
### WebSockets (Win7 and earlier)
.Net Core 1.1 does not support WebSockets on Win7 and earlier. Track the issue [here](https://github.com/dotnet/corefx/issues/9503).

+ 13
- 6
build.bat View File

@@ -1,8 +1,15 @@
@echo Off @echo Off
dotnet restore dotnet restore
dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts"
dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts"
dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts"
dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts"
dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts"
dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts"
dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"

REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"
REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%"

+ 2
- 2
docs/guides/samples.md View File

@@ -13,8 +13,8 @@ title: Samples


#### Changing the bot's status #### Changing the bot's status


[!code-sharp[Bot Status](samples/faq/status.cs)]
[!code-csharp[Bot Status](samples/faq/status.cs)]


#### Sending a message to a channel #### Sending a message to a channel


[!code-csharp[Message to Channel](samples/faq/send_message.cs)]
[!code-csharp[Message to Channel](samples/faq/send_message.cs)]

+ 3
- 6
docs/guides/samples/faq/status.cs View File

@@ -1,8 +1,5 @@
public async Task ModifyStatus() public async Task ModifyStatus()
{ {
await (await _client.GetCurrentUserAsync()).ModifyStatusAsync(x =>
{
x.Status = UserStatus.Idle;
x.Game = new Game("Type !help for help");
});
}
await _client.SetStatus(UserStatus.Idle);
await _client.SetGame("Type !help for help");
}

+ 0
- 3
global.json View File

@@ -1,3 +0,0 @@
{
"projects": [ "src", "test" ]
}

+ 1
- 1
src/Discord.Net.Commands/Attributes/CommandAttribute.cs View File

@@ -6,7 +6,7 @@ namespace Discord.Commands
public class CommandAttribute : Attribute public class CommandAttribute : Attribute
{ {
public string Text { get; } public string Text { get; }
public RunMode RunMode { get; set; } = RunMode.Sync;
public RunMode RunMode { get; set; } = RunMode.Default;


public CommandAttribute() public CommandAttribute()
{ {


+ 75
- 0
src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Commands
{
/// <summary>
/// This attribute requires that the bot has a speicifed permission in the channel a command is invoked in.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequireBotPermissionAttribute : PreconditionAttribute
{
public GuildPermission? GuildPermission { get; }
public ChannelPermission? ChannelPermission { get; }

/// <summary>
/// Require that the bot account has a specified GuildPermission
/// </summary>
/// <remarks>This precondition will always fail if the command is being invoked in a private channel.</remarks>
/// <param name="permission">The GuildPermission that the bot must have. Multiple permissions can be specified by ORing or ANDing the permissions together.</param>
public RequireBotPermissionAttribute(GuildPermission permission)
{
GuildPermission = permission;
ChannelPermission = null;
}
/// <summary>
/// Require that the bot account has a specified ChannelPermission.
/// </summary>
/// <param name="permission">The ChannelPermission that the bot must have. Multiple permissions can be specified by ORing or ANDing the permissions together.</param>
/// <example>
/// <code language="c#">
/// [Command("permission")]
/// [RequireBotPermission(ChannelPermission.ManageMessages)]
/// public async Task Purge()
/// {
/// }
/// </code>
/// </example>
public RequireBotPermissionAttribute(ChannelPermission permission)
{
ChannelPermission = permission;
GuildPermission = null;
}

public override async Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map)
{
var guildUser = await context.Guild.GetCurrentUserAsync();

if (GuildPermission.HasValue)
{
if (guildUser == null)
return PreconditionResult.FromError("Command must be used in a guild channel");
if (!guildUser.GuildPermissions.Has(GuildPermission.Value))
return PreconditionResult.FromError($"Command requires guild permission {GuildPermission.Value}");
}

if (ChannelPermission.HasValue)
{
var guildChannel = context.Channel as IGuildChannel;

ChannelPermissions perms;
if (guildChannel != null)
perms = guildUser.GetPermissions(guildChannel);
else
perms = ChannelPermissions.All(guildChannel);

if (!perms.Has(ChannelPermission.Value))
return PreconditionResult.FromError($"Command requires channel permission {ChannelPermission.Value}");
}

return PreconditionResult.FromSuccess();
}
}
}

+ 16
- 0
src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs View File

@@ -11,11 +11,27 @@ namespace Discord.Commands
Group = 0x04 Group = 0x04
} }


/// <summary>
/// Require that the command be invoked in a specified context.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequireContextAttribute : PreconditionAttribute public class RequireContextAttribute : PreconditionAttribute
{ {
public ContextType Contexts { get; } public ContextType Contexts { get; }


/// <summary>
/// Require that the command be invoked in a specified context.
/// </summary>
/// <param name="contexts">The type of context the command can be invoked in. Multiple contexts can be speicifed by ORing the contexts together.</param>
/// <example>
/// <code language="c#">
/// [Command("private_only")]
/// [RequireContext(ContextType.DM | ContextType.Group)]
/// public async Task PrivateOnly()
/// {
/// }
/// </code>
/// </example>
public RequireContextAttribute(ContextType contexts) public RequireContextAttribute(ContextType contexts)
{ {
Contexts = contexts; Contexts = contexts;


+ 23
- 0
src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Discord;

namespace Discord.Commands
{
/// <summary>
/// Require that the command is invoked by the owner of the bot.
/// </summary>
/// <remarks>This precondition will only work if the bot is a bot account.</remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequireOwnerAttribute : PreconditionAttribute
{
public override async Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map)
{
var application = await context.Client.GetApplicationInfoAsync();
if (context.User.Id == application.Owner.Id) return PreconditionResult.FromSuccess();
return PreconditionResult.FromError("Command can only be run by the owner of the bot");
}
}
}

src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs → src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs View File

@@ -3,18 +3,40 @@ using System.Threading.Tasks;


namespace Discord.Commands namespace Discord.Commands
{ {
/// <summary>
/// This attribute requires that the user invoking the command has a specified permission.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequirePermissionAttribute : PreconditionAttribute
public class RequireUserPermissionAttribute : PreconditionAttribute
{ {
public GuildPermission? GuildPermission { get; } public GuildPermission? GuildPermission { get; }
public ChannelPermission? ChannelPermission { get; } public ChannelPermission? ChannelPermission { get; }


public RequirePermissionAttribute(GuildPermission permission)
/// <summary>
/// Require that the user invoking the command has a specified GuildPermission
/// </summary>
/// <remarks>This precondition will always fail if the command is being invoked in a private channel.</remarks>
/// <param name="permission">The GuildPermission that the user must have. Multiple permissions can be specified by ORing or ANDing the permissions together.</param>
public RequireUserPermissionAttribute(GuildPermission permission)
{ {
GuildPermission = permission; GuildPermission = permission;
ChannelPermission = null; ChannelPermission = null;
} }
public RequirePermissionAttribute(ChannelPermission permission)
/// <summary>
/// Require that the user invoking the command has a specified ChannelPermission.
/// </summary>
/// <param name="permission">The ChannelPermission that the user must have. Multiple permissions can be specified by ORing or ANDing the permissions together.</param>
/// <example>
/// <code language="c#">
/// [Command("permission")]
/// [RequireUserPermission(ChannelPermission.ReadMessageHistory & ChannelPermission.ReadMessages)]
/// public async Task HasPermission()
/// {
/// await ReplyAsync("You can read messages and the message history!");
/// }
/// </code>
/// </example>
public RequireUserPermissionAttribute(ChannelPermission permission)
{ {
ChannelPermission = permission; ChannelPermission = permission;
GuildPermission = null; GuildPermission = null;

+ 120
- 0
src/Discord.Net.Commands/Builders/CommandBuilder.cs View File

@@ -0,0 +1,120 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Discord.Commands.Builders
{
public class CommandBuilder
{
private readonly List<PreconditionAttribute> _preconditions;
private readonly List<ParameterBuilder> _parameters;
private readonly List<string> _aliases;

public ModuleBuilder Module { get; }
internal Func<CommandContext, object[], IDependencyMap, Task> Callback { get; set; }

public string Name { get; set; }
public string Summary { get; set; }
public string Remarks { get; set; }
public RunMode RunMode { get; set; }
public int Priority { get; set; }

public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions;
public IReadOnlyList<ParameterBuilder> Parameters => _parameters;
public IReadOnlyList<string> Aliases => _aliases;

//Automatic
internal CommandBuilder(ModuleBuilder module)
{
Module = module;

_preconditions = new List<PreconditionAttribute>();
_parameters = new List<ParameterBuilder>();
_aliases = new List<string>();
}
//User-defined
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<CommandContext, object[], IDependencyMap, Task> callback)
: this(module)
{
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias));
Discord.Preconditions.NotNull(callback, nameof(callback));

Callback = callback;
_aliases.Add(primaryAlias);
}

public CommandBuilder WithName(string name)
{
Name = name;
return this;
}
public CommandBuilder WithSummary(string summary)
{
Summary = summary;
return this;
}
public CommandBuilder WithRemarks(string remarks)
{
Remarks = remarks;
return this;
}
public CommandBuilder WithRunMode(RunMode runMode)
{
RunMode = runMode;
return this;
}
public CommandBuilder WithPriority(int priority)
{
Priority = priority;
return this;
}

public CommandBuilder AddAliases(params string[] aliases)
{
_aliases.AddRange(aliases);
return this;
}
public CommandBuilder AddPrecondition(PreconditionAttribute precondition)
{
_preconditions.Add(precondition);
return this;
}
public CommandBuilder AddParameter(string name, Type type, Action<ParameterBuilder> createFunc)
{
var param = new ParameterBuilder(this, name, type);
createFunc(param);
_parameters.Add(param);
return this;
}
internal CommandBuilder AddParameter(Action<ParameterBuilder> createFunc)
{
var param = new ParameterBuilder(this);
createFunc(param);
_parameters.Add(param);
return this;
}

internal CommandInfo Build(ModuleInfo info, CommandService service)
{
//Default name to first alias
if (Name == null)
Name = _aliases[0];

if (_parameters.Count > 0)
{
var lastParam = _parameters[_parameters.Count - 1];

var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple);
if ((firstMultipleParam != null) && (firstMultipleParam != lastParam))
throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag.");
var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder);
if ((firstRemainderParam != null) && (firstRemainderParam != lastParam))
throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag.");
}

return new CommandInfo(this, info, service);
}
}
}

+ 109
- 0
src/Discord.Net.Commands/Builders/ModuleBuilder.cs View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord.Commands.Builders
{
public class ModuleBuilder
{
private readonly List<CommandBuilder> _commands;
private readonly List<ModuleBuilder> _submodules;
private readonly List<PreconditionAttribute> _preconditions;
private readonly List<string> _aliases;

public CommandService Service { get; }
public ModuleBuilder Parent { get; }
public string Name { get; set; }
public string Summary { get; set; }
public string Remarks { get; set; }

public IReadOnlyList<CommandBuilder> Commands => _commands;
public IReadOnlyList<ModuleBuilder> Modules => _submodules;
public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions;
public IReadOnlyList<string> Aliases => _aliases;

//Automatic
internal ModuleBuilder(CommandService service, ModuleBuilder parent)
{
Service = service;
Parent = parent;

_commands = new List<CommandBuilder>();
_submodules = new List<ModuleBuilder>();
_preconditions = new List<PreconditionAttribute>();
_aliases = new List<string>();
}
//User-defined
internal ModuleBuilder(CommandService service, ModuleBuilder parent, string primaryAlias)
: this(service, parent)
{
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias));

_aliases = new List<string> { primaryAlias };
}

public ModuleBuilder WithName(string name)
{
Name = name;
return this;
}
public ModuleBuilder WithSummary(string summary)
{
Summary = summary;
return this;
}
public ModuleBuilder WithRemarks(string remarks)
{
Remarks = remarks;
return this;
}

public ModuleBuilder AddAlias(params string[] newAliases)
{
_aliases.AddRange(newAliases);
return this;
}
public ModuleBuilder AddPrecondition(PreconditionAttribute precondition)
{
_preconditions.Add(precondition);
return this;
}
public ModuleBuilder AddCommand(string primaryAlias, Func<CommandContext, object[], IDependencyMap, Task> callback, Action<CommandBuilder> createFunc)
{
var builder = new CommandBuilder(this, primaryAlias, callback);
createFunc(builder);
_commands.Add(builder);
return this;
}
internal ModuleBuilder AddCommand(Action<CommandBuilder> createFunc)
{
var builder = new CommandBuilder(this);
createFunc(builder);
_commands.Add(builder);
return this;
}
public ModuleBuilder AddModule(string primaryAlias, Action<ModuleBuilder> createFunc)
{
var builder = new ModuleBuilder(Service, this, primaryAlias);
createFunc(builder);
_submodules.Add(builder);
return this;
}
internal ModuleBuilder AddModule(Action<ModuleBuilder> createFunc)
{
var builder = new ModuleBuilder(Service, this);
createFunc(builder);
_submodules.Add(builder);
return this;
}

public ModuleInfo Build(CommandService service)
{
//Default name to first alias
if (Name == null)
Name = _aliases[0];

return new ModuleInfo(this, service);
}
}
}

+ 232
- 0
src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs View File

@@ -0,0 +1,232 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

using Discord.Commands.Builders;

namespace Discord.Commands
{
internal static class ModuleClassBuilder
{
private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo();

public static IEnumerable<TypeInfo> Search(Assembly assembly)
{
foreach (var type in assembly.ExportedTypes)
{
var typeInfo = type.GetTypeInfo();
if (IsValidModuleDefinition(typeInfo) &&
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute)))
{
yield return typeInfo;
}
}
}

public static Dictionary<Type, ModuleInfo> Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service);
public static Dictionary<Type, ModuleInfo> Build(IEnumerable<TypeInfo> validTypes, CommandService service)
{
if (!validTypes.Any())
throw new InvalidOperationException("Could not find any valid modules from the given selection");
var topLevelGroups = validTypes.Where(x => x.DeclaringType == null);
var subGroups = validTypes.Intersect(topLevelGroups);

var builtTypes = new List<TypeInfo>();

var result = new Dictionary<Type, ModuleInfo>();

foreach (var typeInfo in topLevelGroups)
{
// TODO: This shouldn't be the case; may be safe to remove?
if (result.ContainsKey(typeInfo.AsType()))
continue;

var module = new ModuleBuilder(service, null);

BuildModule(module, typeInfo, service);
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);

result[typeInfo.AsType()] = module.Build(service);
}

return result;
}

private static void BuildSubTypes(ModuleBuilder builder, IEnumerable<TypeInfo> subTypes, List<TypeInfo> builtTypes, CommandService service)
{
foreach (var typeInfo in subTypes)
{
if (!IsValidModuleDefinition(typeInfo))
continue;
if (builtTypes.Contains(typeInfo))
continue;
builder.AddModule((module) => {
BuildModule(module, typeInfo, service);
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
});

builtTypes.Add(typeInfo);
}
}

private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service)
{
var attributes = typeInfo.GetCustomAttributes();

foreach (var attribute in attributes)
{
// TODO: C#7 type switch
if (attribute is NameAttribute)
builder.Name = (attribute as NameAttribute).Text;
else if (attribute is SummaryAttribute)
builder.Summary = (attribute as SummaryAttribute).Text;
else if (attribute is RemarksAttribute)
builder.Remarks = (attribute as RemarksAttribute).Text;
else if (attribute is AliasAttribute)
builder.AddAlias((attribute as AliasAttribute).Aliases);
else if (attribute is GroupAttribute)
{
var groupAttr = attribute as GroupAttribute;
builder.Name = builder.Name ?? groupAttr.Prefix;
builder.AddAlias(groupAttr.Prefix);
}
else if (attribute is PreconditionAttribute)
builder.AddPrecondition(attribute as PreconditionAttribute);
}

if (builder.Name == null)
builder.Name = typeInfo.Name;

var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x));

foreach (var method in validCommands)
{
builder.AddCommand((command) => {
BuildCommand(command, typeInfo, method, service);
});
}
}

private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service)
{
var attributes = method.GetCustomAttributes();
foreach (var attribute in attributes)
{
// TODO: C#7 type switch
if (attribute is CommandAttribute)
{
var cmdAttr = attribute as CommandAttribute;
builder.AddAliases(cmdAttr.Text);
builder.RunMode = cmdAttr.RunMode;
builder.Name = builder.Name ?? cmdAttr.Text;
}
else if (attribute is NameAttribute)
builder.Name = (attribute as NameAttribute).Text;
else if (attribute is PriorityAttribute)
builder.Priority = (attribute as PriorityAttribute).Priority;
else if (attribute is SummaryAttribute)
builder.Summary = (attribute as SummaryAttribute).Text;
else if (attribute is RemarksAttribute)
builder.Remarks = (attribute as RemarksAttribute).Text;
else if (attribute is AliasAttribute)
builder.AddAliases((attribute as AliasAttribute).Aliases);
else if (attribute is PreconditionAttribute)
builder.AddPrecondition(attribute as PreconditionAttribute);
}

if (builder.Name == null)
builder.Name = method.Name;

var parameters = method.GetParameters();
int pos = 0, count = parameters.Length;
foreach (var paramInfo in parameters)
{
builder.AddParameter((parameter) => {
BuildParameter(parameter, paramInfo, pos++, count, service);
});
}

var createInstance = ReflectionUtils.CreateBuilder<ModuleBase>(typeInfo, service);

builder.Callback = (ctx, args, map) => {
var instance = createInstance(map);
instance.Context = ctx;
try
{
return method.Invoke(instance, args) as Task ?? Task.CompletedTask;
}
finally{
(instance as IDisposable)?.Dispose();
}
};
}

private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service)
{
var attributes = paramInfo.GetCustomAttributes();
var paramType = paramInfo.ParameterType;

builder.Name = paramInfo.Name;

builder.IsOptional = paramInfo.IsOptional;
builder.DefaultValue = paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null;

foreach (var attribute in attributes)
{
// TODO: C#7 type switch
if (attribute is SummaryAttribute)
builder.Summary = (attribute as SummaryAttribute).Text;
else if (attribute is ParamArrayAttribute)
{
builder.IsMultiple = true;
paramType = paramType.GetElementType();
}
else if (attribute is RemainderAttribute)
{
if (position != count-1)
throw new InvalidOperationException("Remainder parameters must be the last parameter in a command.");
builder.IsRemainder = true;
}
}

var reader = service.GetTypeReader(paramType);
if (reader == null)
{
var paramTypeInfo = paramType.GetTypeInfo();
if (paramTypeInfo.IsEnum)
{
reader = EnumTypeReader.GetReader(paramType);
service.AddTypeReader(paramType, reader);
}
else
{
throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?");
}
}

builder.ParameterType = paramType;
builder.TypeReader = reader;
}

private static bool IsValidModuleDefinition(TypeInfo typeInfo)
{
return _moduleTypeInfo.IsAssignableFrom(typeInfo) &&
!typeInfo.IsAbstract;
}

private static bool IsValidCommandDefinition(MethodInfo methodInfo)
{
return methodInfo.IsDefined(typeof(CommandAttribute)) &&
methodInfo.ReturnType == typeof(Task) &&
!methodInfo.IsStatic &&
!methodInfo.IsGenericMethod;
}
}
}

+ 79
- 0
src/Discord.Net.Commands/Builders/ParameterBuilder.cs View File

@@ -0,0 +1,79 @@
using System;
using System.Reflection;

namespace Discord.Commands.Builders
{
public class ParameterBuilder
{
public CommandBuilder Command { get; }
public string Name { get; internal set; }
public Type ParameterType { get; internal set; }

public TypeReader TypeReader { get; set; }
public bool IsOptional { get; set; }
public bool IsRemainder { get; set; }
public bool IsMultiple { get; set; }
public object DefaultValue { get; set; }
public string Summary { get; set; }

//Automatic
internal ParameterBuilder(CommandBuilder command)
{
Command = command;
}
//User-defined
internal ParameterBuilder(CommandBuilder command, string name, Type type)
: this(command)
{
Preconditions.NotNull(name, nameof(name));

Name = name;
SetType(type);
}

internal void SetType(Type type)
{
TypeReader = Command.Module.Service.GetTypeReader(type);

if (type.GetTypeInfo().IsValueType)
DefaultValue = Activator.CreateInstance(type);
else if (type.IsArray)
type = ParameterType.GetElementType();
ParameterType = type;
}
public ParameterBuilder WithSummary(string summary)
{
Summary = summary;
return this;
}
public ParameterBuilder WithDefault(object defaultValue)
{
DefaultValue = defaultValue;
return this;
}
public ParameterBuilder WithIsOptional(bool isOptional)
{
IsOptional = isOptional;
return this;
}
public ParameterBuilder WithIsRemainder(bool isRemainder)
{
IsRemainder = isRemainder;
return this;
}
public ParameterBuilder WithIsMultiple(bool isMultiple)
{
IsMultiple = isMultiple;
return this;
}

internal ParameterInfo Build(CommandInfo info)
{
if (TypeReader == null)
throw new InvalidOperationException($"No default TypeReader found, one must be specified");

return new ParameterInfo(this, info, Command.Module.Service);
}
}
}

+ 0
- 284
src/Discord.Net.Commands/CommandInfo.cs View File

@@ -1,284 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Discord.Commands
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class CommandInfo
{
private static readonly MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();
private readonly Func<CommandContext, object[], IDependencyMap, Task> _action;

public MethodInfo Source { get; }
public ModuleInfo Module { get; }
public string Name { get; }
public string Summary { get; }
public string Remarks { get; }
public string Text { get; }
public int Priority { get; }
public bool HasVarArgs { get; }
public RunMode RunMode { get; }
public IReadOnlyList<string> Aliases { get; }
public IReadOnlyList<CommandParameter> Parameters { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }

internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix)
{
try
{
Source = source;
Module = module;

Name = source.Name;

if (attribute.Text == null)
Text = groupPrefix;
RunMode = attribute.RunMode;

if (groupPrefix != "")
groupPrefix += " ";

if (attribute.Text != null)
Text = groupPrefix + attribute.Text;

var aliasesBuilder = ImmutableArray.CreateBuilder<string>();

aliasesBuilder.Add(Text);

var aliasesAttr = source.GetCustomAttribute<AliasAttribute>();
if (aliasesAttr != null)
aliasesBuilder.AddRange(aliasesAttr.Aliases.Select(x => groupPrefix + x));

Aliases = aliasesBuilder.ToImmutable();

var nameAttr = source.GetCustomAttribute<NameAttribute>();
if (nameAttr != null)
Name = nameAttr.Text;

var summary = source.GetCustomAttribute<SummaryAttribute>();
if (summary != null)
Summary = summary.Text;

var remarksAttr = source.GetCustomAttribute<RemarksAttribute>();
if (remarksAttr != null)
Remarks = remarksAttr.Text;

var priorityAttr = source.GetCustomAttribute<PriorityAttribute>();
Priority = priorityAttr?.Priority ?? 0;

Parameters = BuildParameters(source);
HasVarArgs = Parameters.Count > 0 ? Parameters[Parameters.Count - 1].IsMultiple : false;
Preconditions = BuildPreconditions(source);
_action = BuildAction(source);
}
catch (Exception ex)
{
throw new Exception($"Failed to build command {source.DeclaringType.FullName}.{source.Name}", ex);
}
}

public async Task<PreconditionResult> CheckPreconditions(CommandContext context, IDependencyMap map = null)
{
if (map == null)
map = DependencyMap.Empty;

foreach (PreconditionAttribute precondition in Module.Preconditions)
{
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}

foreach (PreconditionAttribute precondition in Preconditions)
{
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}

return PreconditionResult.FromSuccess();
}

public async Task<ParseResult> Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null)
{
if (!searchResult.IsSuccess)
return ParseResult.FromError(searchResult);
if (preconditionResult != null && !preconditionResult.Value.IsSuccess)
return ParseResult.FromError(preconditionResult.Value);

string input = searchResult.Text;
var matchingAliases = Aliases.Where(alias => input.StartsWith(alias));
string matchingAlias = "";
foreach (string alias in matchingAliases)
{
if (alias.Length > matchingAlias.Length)
matchingAlias = alias;
}
input = input.Substring(matchingAlias.Length);

return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false);
}
public Task<ExecuteResult> Execute(CommandContext context, ParseResult parseResult, IDependencyMap map)
{
if (!parseResult.IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult));

var argList = new object[parseResult.ArgValues.Count];
for (int i = 0; i < parseResult.ArgValues.Count; i++)
{
if (!parseResult.ArgValues[i].IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i]));
argList[i] = parseResult.ArgValues[i].Values.First().Value;
}
var paramList = new object[parseResult.ParamValues.Count];
for (int i = 0; i < parseResult.ParamValues.Count; i++)
{
if (!parseResult.ParamValues[i].IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i]));
paramList[i] = parseResult.ParamValues[i].Values.First().Value;
}

return Execute(context, argList, paramList, map);
}
public async Task<ExecuteResult> Execute(CommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IDependencyMap map)
{
if (map == null)
map = DependencyMap.Empty;

try
{
var args = GenerateArgs(argList, paramList);
switch (RunMode)
{
case RunMode.Sync: //Always sync
await _action(context, args, map).ConfigureAwait(false);
break;
case RunMode.Mixed: //Sync until first await statement
var t1 = _action(context, args, map);
break;
case RunMode.Async: //Always async
var t2 = Task.Run(() => _action(context, args, map));
break;
}
return ExecuteResult.FromSuccess();
}
catch (Exception ex)
{
return ExecuteResult.FromError(ex);
}
}

private IReadOnlyList<PreconditionAttribute> BuildPreconditions(MethodInfo methodInfo)
{
return methodInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
}

private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo)
{
var parameters = methodInfo.GetParameters();

var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length);
for (int i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
var type = parameter.ParameterType;

//Detect 'params'
bool isMultiple = parameter.GetCustomAttribute<ParamArrayAttribute>() != null;
if (isMultiple)
type = type.GetElementType();

var reader = Module.Service.GetTypeReader(type);
var typeInfo = type.GetTypeInfo();

//Detect enums
if (reader == null && typeInfo.IsEnum)
{
reader = EnumTypeReader.GetReader(type);
Module.Service.AddTypeReader(type, reader);
}

if (reader == null)
throw new InvalidOperationException($"{type.FullName} is not supported as a command parameter, are you missing a TypeReader?");

bool isRemainder = parameter.GetCustomAttribute<RemainderAttribute>() != null;
if (isRemainder && i != parameters.Length - 1)
throw new InvalidOperationException("Remainder parameters must be the last parameter in a command.");

string name = parameter.Name;
string summary = parameter.GetCustomAttribute<SummaryAttribute>()?.Text;
bool isOptional = parameter.IsOptional;
object defaultValue = parameter.HasDefaultValue ? parameter.DefaultValue : null;

paramBuilder.Add(new CommandParameter(parameters[i], name, summary, type, reader, isOptional, isRemainder, isMultiple, defaultValue));
}
return paramBuilder.ToImmutable();
}
private Func<CommandContext, object[], IDependencyMap, Task> BuildAction(MethodInfo methodInfo)
{
if (methodInfo.ReturnType != typeof(Task))
throw new InvalidOperationException("Commands must return a non-generic Task.");

return (context, args, map) =>
{
var instance = Module.CreateInstance(map);
instance.Context = context;
try
{
return methodInfo.Invoke(instance, args) as Task ?? Task.CompletedTask;
}
finally
{
(instance as IDisposable)?.Dispose();
}
};
}

private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList)
{
int argCount = Parameters.Count;
var array = new object[Parameters.Count];
if (HasVarArgs)
argCount--;

int i = 0;
foreach (var arg in argList)
{
if (i == argCount)
throw new InvalidOperationException("Command was invoked with too many parameters");
array[i++] = arg;
}
if (i < argCount)
throw new InvalidOperationException("Command was invoked with too few parameters");

if (HasVarArgs)
{
var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].ElementType, t =>
{
var method = _convertParamsMethod.MakeGenericMethod(t);
return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>));
});
array[i] = func(paramsList);
}

return array;
}

private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList)
=> paramsList.Cast<T>().ToArray();

public override string ToString() => Name;
private string DebuggerDisplay => $"{Module.Name}.{Name} ({Text})";
}
}

+ 5
- 7
src/Discord.Net.Commands/CommandParser.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Commands
public static async Task<ParseResult> ParseArgs(CommandInfo command, CommandContext context, string input, int startPos) public static async Task<ParseResult> ParseArgs(CommandInfo command, CommandContext context, string input, int startPos)
{ {
CommandParameter curParam = null;
ParameterInfo curParam = null;
StringBuilder argBuilder = new StringBuilder(input.Length); StringBuilder argBuilder = new StringBuilder(input.Length);
int endPos = input.Length; int endPos = input.Length;
var curPart = ParserPart.None; var curPart = ParserPart.None;
@@ -65,7 +65,9 @@ namespace Discord.Commands
return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments."); return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments.");
else else
{ {
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null;
if (curParam == null)
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null;

if (curParam != null && curParam.IsRemainder) if (curParam != null && curParam.IsRemainder)
{ {
argBuilder.Append(c); argBuilder.Append(c);
@@ -116,11 +118,7 @@ namespace Discord.Commands
{ {
paramList.Add(typeReaderResult); paramList.Add(typeReaderResult);


if (curPos == endPos)
{
curParam = null;
curPart = ParserPart.None;
}
curPart = ParserPart.None;
} }
else else
{ {


+ 85
- 53
src/Discord.Net.Commands/CommandService.cs View File

@@ -7,24 +7,30 @@ using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


using Discord.Commands.Builders;

namespace Discord.Commands namespace Discord.Commands
{ {
public class CommandService public class CommandService
{ {
private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo();

private readonly SemaphoreSlim _moduleLock; private readonly SemaphoreSlim _moduleLock;
private readonly ConcurrentDictionary<Type, ModuleInfo> _moduleDefs;
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs;
private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders;
private readonly ConcurrentBag<ModuleInfo> _moduleDefs;
private readonly CommandMap _map; private readonly CommandMap _map;


public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x.Value);
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Value.Commands);
internal readonly bool _caseSensitive;
internal readonly RunMode _defaultRunMode;


public CommandService()
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x);
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands);

public CommandService() : this(new CommandServiceConfig()) { }
public CommandService(CommandServiceConfig config)
{ {
_moduleLock = new SemaphoreSlim(1, 1); _moduleLock = new SemaphoreSlim(1, 1);
_moduleDefs = new ConcurrentDictionary<Type, ModuleInfo>();
_typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>();
_moduleDefs = new ConcurrentBag<ModuleInfo>();
_map = new CommandMap(); _map = new CommandMap();
_typeReaders = new ConcurrentDictionary<Type, TypeReader> _typeReaders = new ConcurrentDictionary<Type, TypeReader>
{ {
@@ -62,103 +68,129 @@ namespace Discord.Commands
[typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(), [typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(),
[typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(), [typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(),
}; };
_caseSensitive = config.CaseSensitiveCommands;
_defaultRunMode = config.DefaultRunMode;
} }


//Modules //Modules
public async Task<ModuleInfo> AddModule<T>()
public async Task<ModuleInfo> CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc)
{ {
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
var typeInfo = typeof(T).GetTypeInfo();
if (!_moduleTypeInfo.IsAssignableFrom(typeInfo))
throw new ArgumentException($"Modules must inherit ModuleBase.");
var builder = new ModuleBuilder(this, null, primaryAlias);
buildFunc(builder);


if (typeInfo.IsAbstract)
throw new InvalidOperationException("Modules must not be abstract.");
var module = builder.Build(this);
return LoadModuleInternal(module);
}
finally
{
_moduleLock.Release();
}
}
public async Task<ModuleInfo> AddModuleAsync<T>()
{
await _moduleLock.WaitAsync().ConfigureAwait(false);
try
{
var typeInfo = typeof(T).GetTypeInfo();


if (_moduleDefs.ContainsKey(typeof(T)))
if (_typedModuleDefs.ContainsKey(typeof(T)))
throw new ArgumentException($"This module has already been added."); throw new ArgumentException($"This module has already been added.");


return AddModuleInternal(typeInfo);
var module = ModuleClassBuilder.Build(this, typeInfo).FirstOrDefault();

if (module.Value == default(ModuleInfo))
throw new InvalidOperationException($"Could not build the module {typeof(T).FullName}, did you pass an invalid type?");

_typedModuleDefs[module.Key] = module.Value;
return LoadModuleInternal(module.Value);
} }
finally finally
{ {
_moduleLock.Release(); _moduleLock.Release();
} }
} }
public async Task<IEnumerable<ModuleInfo>> AddModules(Assembly assembly)
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly)
{ {
var moduleDefs = ImmutableArray.CreateBuilder<ModuleInfo>();
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
foreach (var type in assembly.ExportedTypes)
var types = ModuleClassBuilder.Search(assembly).ToArray();
var moduleDefs = ModuleClassBuilder.Build(types, this);

foreach (var info in moduleDefs)
{ {
if (!_moduleDefs.ContainsKey(type))
{
var typeInfo = type.GetTypeInfo();
if (_moduleTypeInfo.IsAssignableFrom(typeInfo))
{
var dontAutoLoad = typeInfo.GetCustomAttribute<DontAutoLoadAttribute>();
if (dontAutoLoad == null && !typeInfo.IsAbstract)
moduleDefs.Add(AddModuleInternal(typeInfo));
}
}
_typedModuleDefs[info.Key] = info.Value;
LoadModuleInternal(info.Value);
} }
return moduleDefs.ToImmutable();

return moduleDefs.Select(x => x.Value).ToImmutableArray();
} }
finally finally
{ {
_moduleLock.Release(); _moduleLock.Release();
} }
} }
private ModuleInfo AddModuleInternal(TypeInfo typeInfo)
private ModuleInfo LoadModuleInternal(ModuleInfo module)
{ {
var moduleDef = new ModuleInfo(typeInfo, this);
_moduleDefs[typeInfo.AsType()] = moduleDef;
_moduleDefs.Add(module);


foreach (var cmd in moduleDef.Commands)
_map.AddCommand(cmd);
foreach (var command in module.Commands)
_map.AddCommand(command);


return moduleDef;
foreach (var submodule in module.Submodules)
LoadModuleInternal(submodule);
return module;
} }


public async Task<bool> RemoveModule(ModuleInfo module)
public async Task<bool> RemoveModuleAsync(ModuleInfo module)
{ {
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
return RemoveModuleInternal(module.Source.BaseType);
return RemoveModuleInternal(module);
} }
finally finally
{ {
_moduleLock.Release(); _moduleLock.Release();
} }
} }
public async Task<bool> RemoveModule<T>()
public async Task<bool> RemoveModuleAsync<T>()
{ {
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
return RemoveModuleInternal(typeof(T));
ModuleInfo module;
_typedModuleDefs.TryGetValue(typeof(T), out module);
if (module == default(ModuleInfo))
return false;
return RemoveModuleInternal(module);
} }
finally finally
{ {
_moduleLock.Release(); _moduleLock.Release();
} }
} }
private bool RemoveModuleInternal(Type type)
private bool RemoveModuleInternal(ModuleInfo module)
{ {
ModuleInfo unloadedModule;
if (_moduleDefs.TryRemove(type, out unloadedModule))
var defsRemove = module;
if (!_moduleDefs.TryTake(out defsRemove))
return false;
foreach (var cmd in module.Commands)
_map.RemoveCommand(cmd);

foreach (var submodule in module.Submodules)
{ {
foreach (var cmd in unloadedModule.Commands)
_map.RemoveCommand(cmd);
return true;
RemoveModuleInternal(submodule);
} }
else
return false;

return true;
} }


//Type Readers //Type Readers
@@ -182,7 +214,7 @@ namespace Discord.Commands
public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos));
public SearchResult Search(CommandContext context, string input) public SearchResult Search(CommandContext context, string input)
{ {
string lowerInput = input.ToLowerInvariant();
input = _caseSensitive ? input : input.ToLowerInvariant();
var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray(); var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray();
if (matches.Length > 0) if (matches.Length > 0)
@@ -191,9 +223,9 @@ namespace Discord.Commands
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
} }


public Task<IResult> Execute(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
=> Execute(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling);
public async Task<IResult> Execute(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
public Task<IResult> ExecuteAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling);
public async Task<IResult> ExecuteAsync(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
{ {
dependencyMap = dependencyMap ?? DependencyMap.Empty; dependencyMap = dependencyMap ?? DependencyMap.Empty;


@@ -204,7 +236,7 @@ namespace Discord.Commands
var commands = searchResult.Commands; var commands = searchResult.Commands;
for (int i = commands.Count - 1; i >= 0; i--) for (int i = commands.Count - 1; i >= 0; i--)
{ {
var preconditionResult = await commands[i].CheckPreconditions(context, dependencyMap).ConfigureAwait(false);
var preconditionResult = await commands[i].CheckPreconditionsAsync(context, dependencyMap).ConfigureAwait(false);
if (!preconditionResult.IsSuccess) if (!preconditionResult.IsSuccess)
{ {
if (commands.Count == 1) if (commands.Count == 1)
@@ -213,7 +245,7 @@ namespace Discord.Commands
continue; continue;
} }


var parseResult = await commands[i].Parse(context, searchResult, preconditionResult).ConfigureAwait(false);
var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false);
if (!parseResult.IsSuccess) if (!parseResult.IsSuccess)
{ {
if (parseResult.Error == CommandError.MultipleMatches) if (parseResult.Error == CommandError.MultipleMatches)


+ 10
- 0
src/Discord.Net.Commands/CommandServiceConfig.cs View File

@@ -0,0 +1,10 @@
namespace Discord.Commands
{
public class CommandServiceConfig
{
/// <summary> The default RunMode commands should have, if one is not specified on the Command attribute or builder. </summary>
public RunMode DefaultRunMode { get; set; } = RunMode.Mixed;
/// <summary> Should commands be case-sensitive? </summary>
public bool CaseSensitiveCommands { get; set; } = false;
}
}

+ 41
- 0
src/Discord.Net.Commands/Discord.Net.Commands.csproj View File

@@ -0,0 +1,41 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Description>A Discord.Net extension adding support for bot commands.</Description>
<VersionPrefix>1.0.0-beta2</VersionPrefix>
<TargetFramework>netstandard1.3</TargetFramework>
<AssemblyName>Discord.Net.Commands</AssemblyName>
<PackageTags>discord;discordapp</PackageTags>
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
<EmbeddedResource Include="compiler\resources\**\*" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161104-2</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Label="Configuration">
<SignAssembly>False</SignAssembly>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
<WarningsAsErrors>true</WarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

+ 0
- 19
src/Discord.Net.Commands/Discord.Net.Commands.xproj View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>078dd7e6-943d-4d09-afc2-d2ba58b76c9c</ProjectGuid>
<RootNamespace>Discord.Commands</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

+ 22
- 0
src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;

namespace Discord.Commands
{
public static class IEnumerableExtensions
{
public static IEnumerable<TResult> Permutate<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> set,
IEnumerable<TSecond> others,
Func<TFirst, TSecond, TResult> func)
{
foreach (TFirst elem in set)
{
foreach (TSecond elem2 in others)
{
yield return func(elem, elem2);
}
}
}
}
}

+ 194
- 0
src/Discord.Net.Commands/Info/CommandInfo.cs View File

@@ -0,0 +1,194 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Reflection;

using Discord.Commands.Builders;
using System.Diagnostics;

namespace Discord.Commands
{
[DebuggerDisplay("{Name,nq}")]
public class CommandInfo
{
private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();

private readonly Func<CommandContext, object[], IDependencyMap, Task> _action;

public ModuleInfo Module { get; }
public string Name { get; }
public string Summary { get; }
public string Remarks { get; }
public int Priority { get; }
public bool HasVarArgs { get; }
public RunMode RunMode { get; }

public IReadOnlyList<string> Aliases { get; }
public IReadOnlyList<ParameterInfo> Parameters { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }

internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service)
{
Module = module;
Name = builder.Name;
Summary = builder.Summary;
Remarks = builder.Remarks;

RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode);
Priority = builder.Priority;

// both command and module provide aliases
if (module.Aliases.Count > 0 && builder.Aliases.Count > 0)
Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => second != null ? first + " " + second : first).Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray();
// only module provides aliases
else if (module.Aliases.Count > 0)
Aliases = module.Aliases.Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray();
// only command provides aliases
else if (builder.Aliases.Count > 0)
Aliases = builder.Aliases.Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray();
// neither provide aliases
else
throw new InvalidOperationException("Cannot build a command without any aliases");

Preconditions = builder.Preconditions.ToImmutableArray();

Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray();
HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false;

_action = builder.Callback;
}

public async Task<PreconditionResult> CheckPreconditionsAsync(CommandContext context, IDependencyMap map = null)
{
if (map == null)
map = DependencyMap.Empty;

foreach (PreconditionAttribute precondition in Module.Preconditions)
{
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}

foreach (PreconditionAttribute precondition in Preconditions)
{
var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}

return PreconditionResult.FromSuccess();
}

public async Task<ParseResult> ParseAsync(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null)
{
if (!searchResult.IsSuccess)
return ParseResult.FromError(searchResult);
if (preconditionResult != null && !preconditionResult.Value.IsSuccess)
return ParseResult.FromError(preconditionResult.Value);
string input = searchResult.Text;
var matchingAliases = Aliases.Where(alias => input.StartsWith(alias));
string matchingAlias = "";
foreach (string alias in matchingAliases)
{
if (alias.Length > matchingAlias.Length)
matchingAlias = alias;
}
input = input.Substring(matchingAlias.Length);

return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false);
}

public Task<ExecuteResult> Execute(CommandContext context, ParseResult parseResult, IDependencyMap map)
{
if (!parseResult.IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult));

var argList = new object[parseResult.ArgValues.Count];
for (int i = 0; i < parseResult.ArgValues.Count; i++)
{
if (!parseResult.ArgValues[i].IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i]));
argList[i] = parseResult.ArgValues[i].Values.First().Value;
}
var paramList = new object[parseResult.ParamValues.Count];
for (int i = 0; i < parseResult.ParamValues.Count; i++)
{
if (!parseResult.ParamValues[i].IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i]));
paramList[i] = parseResult.ParamValues[i].Values.First().Value;
}

return ExecuteAsync(context, argList, paramList, map);
}
public async Task<ExecuteResult> ExecuteAsync(CommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IDependencyMap map)
{
if (map == null)
map = DependencyMap.Empty;

try
{
var args = GenerateArgs(argList, paramList);
switch (RunMode)
{
case RunMode.Sync: //Always sync
await _action(context, args, map).ConfigureAwait(false);
break;
case RunMode.Mixed: //Sync until first await statement
var t1 = _action(context, args, map);
break;
case RunMode.Async: //Always async
var t2 = Task.Run(() => _action(context, args, map));
break;
}
return ExecuteResult.FromSuccess();
}
catch (Exception ex)
{
return ExecuteResult.FromError(ex);
}
}

private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList)
{
int argCount = Parameters.Count;
var array = new object[Parameters.Count];
if (HasVarArgs)
argCount--;

int i = 0;
foreach (var arg in argList)
{
if (i == argCount)
throw new InvalidOperationException("Command was invoked with too many parameters");
array[i++] = arg;
}
if (i < argCount)
throw new InvalidOperationException("Command was invoked with too few parameters");

if (HasVarArgs)
{
var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t =>
{
var method = _convertParamsMethod.MakeGenericMethod(t);
return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>));
});
array[i] = func(paramsList);
}

return array;
}

private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList)
=> paramsList.Cast<T>().ToArray();
}
}

+ 96
- 0
src/Discord.Net.Commands/Info/ModuleInfo.cs View File

@@ -0,0 +1,96 @@
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;

using Discord.Commands.Builders;

namespace Discord.Commands
{
public class ModuleInfo
{
public CommandService Service { get; }
public string Name { get; }
public string Summary { get; }
public string Remarks { get; }

public IReadOnlyList<string> Aliases { get; }
public IEnumerable<CommandInfo> Commands { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
public IReadOnlyList<ModuleInfo> Submodules { get; }

internal ModuleInfo(ModuleBuilder builder, CommandService service)
{
Service = service;

Name = builder.Name;
Summary = builder.Summary;
Remarks = builder.Remarks;

Aliases = BuildAliases(builder).ToImmutableArray();
Commands = builder.Commands.Select(x => x.Build(this, service));
Preconditions = BuildPreconditions(builder).ToImmutableArray();

Submodules = BuildSubmodules(builder, service).ToImmutableArray();
}

private static IEnumerable<string> BuildAliases(ModuleBuilder builder)
{
IEnumerable<string> result = null;

Stack<ModuleBuilder> builderStack = new Stack<ModuleBuilder>();
builderStack.Push(builder);

ModuleBuilder parent = builder.Parent;
while (parent != null)
{
builderStack.Push(parent);
parent = parent.Parent;
}

while (builderStack.Count() > 0)
{
ModuleBuilder level = builderStack.Pop(); //get the topmost builder
if (result == null)
{
if (level.Aliases.Count > 0)
result = level.Aliases.ToList(); //create a shallow copy so we don't overwrite the builder unexpectedly
}
else if (result.Count() > level.Aliases.Count)
result = result.Permutate(level.Aliases, (first, second) => first + " " + second);
else
result = level.Aliases.Permutate(result, (second, first) => first + " " + second);
}

if (result == null) //there were no aliases; default to an empty list
result = new List<string>();

return result;
}

private static List<ModuleInfo> BuildSubmodules(ModuleBuilder parent, CommandService service)
{
var result = new List<ModuleInfo>();

foreach (var submodule in parent.Modules)
{
result.Add(submodule.Build(service));
}

return result;
}

private static List<PreconditionAttribute> BuildPreconditions(ModuleBuilder builder)
{
var result = new List<PreconditionAttribute>();

ModuleBuilder parent = builder;
while (parent != null)
{
result.AddRange(parent.Preconditions);
parent = parent.Parent;
}

return result;
}
}
}

src/Discord.Net.Commands/CommandParameter.cs → src/Discord.Net.Commands/Info/ParameterInfo.cs View File

@@ -1,37 +1,40 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;


using Discord.Commands.Builders;

namespace Discord.Commands namespace Discord.Commands
{ {
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class CommandParameter
public class ParameterInfo
{ {
private readonly TypeReader _reader; private readonly TypeReader _reader;


public ParameterInfo Source { get; }
internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service)
{
Command = command;

Name = builder.Name;
Summary = builder.Summary;
IsOptional = builder.IsOptional;
IsRemainder = builder.IsRemainder;
IsMultiple = builder.IsMultiple;

Type = builder.ParameterType;
DefaultValue = builder.DefaultValue;

_reader = builder.TypeReader;
}

public CommandInfo Command { get; }
public string Name { get; } public string Name { get; }
public string Summary { get; } public string Summary { get; }
public bool IsOptional { get; } public bool IsOptional { get; }
public bool IsRemainder { get; } public bool IsRemainder { get; }
public bool IsMultiple { get; } public bool IsMultiple { get; }
public Type ElementType { get; }
public Type Type { get; }
public object DefaultValue { get; } public object DefaultValue { get; }


public CommandParameter(ParameterInfo source, string name, string summary, Type type, TypeReader reader, bool isOptional, bool isRemainder, bool isMultiple, object defaultValue)
{
Source = source;
Name = name;
Summary = summary;
ElementType = type;
_reader = reader;
IsOptional = isOptional;
IsRemainder = isRemainder;
IsMultiple = isMultiple;
DefaultValue = defaultValue;
}

public async Task<TypeReaderResult> Parse(CommandContext context, string input) public async Task<TypeReaderResult> Parse(CommandContext context, string input)
{ {
return await _reader.Read(context, input).ConfigureAwait(false); return await _reader.Read(context, input).ConfigureAwait(false);
@@ -40,4 +43,4 @@ namespace Discord.Commands
public override string ToString() => Name; public override string ToString() => Name;
private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}"; private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}";
} }
}
}

+ 14
- 70
src/Discord.Net.Commands/Map/CommandMap.cs View File

@@ -1,95 +1,39 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;


namespace Discord.Commands namespace Discord.Commands
{ {
internal class CommandMap internal class CommandMap
{ {
static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' };
private readonly object _lockObj = new object();

private readonly ConcurrentDictionary<string, CommandMapNode> _nodes;
private readonly CommandMapNode _root;
private static readonly string[] _blankAliases = new[] { "" };


public CommandMap() public CommandMap()
{ {
_nodes = new ConcurrentDictionary<string, CommandMapNode>();
_root = new CommandMapNode("");
} }


public void AddCommand(CommandInfo command) public void AddCommand(CommandInfo command)
{ {
foreach (string text in command.Aliases)
{
int nextSpace = NextWhitespace(text);
string name;

if (nextSpace == -1)
name = text;
else
name = text.Substring(0, nextSpace);

lock (_lockObj)
{
var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(x));
nextNode.AddCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command);
}
}
foreach (string text in GetAliases(command))
_root.AddCommand(text, 0, command);
} }
public void RemoveCommand(CommandInfo command) public void RemoveCommand(CommandInfo command)
{ {
foreach (string text in command.Aliases)
{
int nextSpace = NextWhitespace(text);
string name;

if (nextSpace == -1)
name = text;
else
name = text.Substring(0, nextSpace);

lock (_lockObj)
{
CommandMapNode nextNode;
if (_nodes.TryGetValue(name, out nextNode))
{
nextNode.RemoveCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command);
if (nextNode.IsEmpty)
_nodes.TryRemove(name, out nextNode);
}
}
}
foreach (string text in GetAliases(command))
_root.RemoveCommand(text, 0, command);
} }


public IEnumerable<CommandInfo> GetCommands(string text) public IEnumerable<CommandInfo> GetCommands(string text)
{ {
int nextSpace = NextWhitespace(text);
string name;

if (nextSpace == -1)
name = text;
else
name = text.Substring(0, nextSpace);

lock (_lockObj)
{
CommandMapNode nextNode;
if (_nodes.TryGetValue(name, out nextNode))
return nextNode.GetCommands(text, nextSpace + 1);
else
return Enumerable.Empty<CommandInfo>();
}
return _root.GetCommands(text, 0);
} }


private static int NextWhitespace(string text)
private IReadOnlyList<string> GetAliases(CommandInfo command)
{ {
int lowest = int.MaxValue;
for (int i = 0; i < _whitespaceChars.Length; i++)
{
int index = text.IndexOf(_whitespaceChars[i]);
if (index != -1 && index < lowest)
lowest = index;
}
return (lowest != int.MaxValue) ? lowest : -1;
var aliases = command.Aliases;
if (aliases.Count == 0)
return _blankAliases;
return aliases;
} }
} }
} }

+ 23
- 4
src/Discord.Net.Commands/Map/CommandMapNode.cs View File

@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;


@@ -6,6 +7,8 @@ namespace Discord.Commands
{ {
internal class CommandMapNode internal class CommandMapNode
{ {
private static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' };

private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; private readonly ConcurrentDictionary<string, CommandMapNode> _nodes;
private readonly string _name; private readonly string _name;
private readonly object _lockObj = new object(); private readonly object _lockObj = new object();
@@ -22,13 +25,17 @@ namespace Discord.Commands


public void AddCommand(string text, int index, CommandInfo command) public void AddCommand(string text, int index, CommandInfo command)
{ {
int nextSpace = text.IndexOf(' ', index);
int nextSpace = NextWhitespace(text, index);
string name; string name;


lock (_lockObj) lock (_lockObj)
{ {
if (text == "") if (text == "")
{
if (_name == "")
throw new InvalidOperationException("Cannot add commands to the root node.");
_commands = _commands.Add(command); _commands = _commands.Add(command);
}
else else
{ {
if (nextSpace == -1) if (nextSpace == -1)
@@ -43,7 +50,7 @@ namespace Discord.Commands
} }
public void RemoveCommand(string text, int index, CommandInfo command) public void RemoveCommand(string text, int index, CommandInfo command)
{ {
int nextSpace = text.IndexOf(' ', index);
int nextSpace = NextWhitespace(text, index);
string name; string name;


lock (_lockObj) lock (_lockObj)
@@ -70,7 +77,7 @@ namespace Discord.Commands


public IEnumerable<CommandInfo> GetCommands(string text, int index) public IEnumerable<CommandInfo> GetCommands(string text, int index)
{ {
int nextSpace = text.IndexOf(' ', index);
int nextSpace = NextWhitespace(text, index);
string name; string name;


var commands = _commands; var commands = _commands;
@@ -92,5 +99,17 @@ namespace Discord.Commands
} }
} }
} }

private static int NextWhitespace(string text, int startIndex)
{
int lowest = int.MaxValue;
for (int i = 0; i < _whitespaceChars.Length; i++)
{
int index = text.IndexOf(_whitespaceChars[i], startIndex);
if (index != -1 && index < lowest)
lowest = index;
}
return (lowest != int.MaxValue) ? lowest : -1;
}
} }
} }

+ 2
- 2
src/Discord.Net.Commands/ModuleBase.cs View File

@@ -6,9 +6,9 @@ namespace Discord.Commands
{ {
public CommandContext Context { get; internal set; } public CommandContext Context { get; internal set; }


protected virtual async Task<IUserMessage> ReplyAsync(string message, bool isTTS = false, RequestOptions options = null)
protected virtual async Task<IUserMessage> ReplyAsync(string message, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null)
{ {
return await Context.Channel.SendMessageAsync(message, isTTS, options).ConfigureAwait(false);
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false);
} }
} }
} }

+ 0
- 85
src/Discord.Net.Commands/ModuleInfo.cs View File

@@ -1,85 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection;

namespace Discord.Commands
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class ModuleInfo
{
internal readonly Func<IDependencyMap, ModuleBase> _builder;

public TypeInfo Source { get; }
public CommandService Service { get; }
public string Name { get; }
public string Prefix { get; }
public string Summary { get; }
public string Remarks { get; }
public IEnumerable<CommandInfo> Commands { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }

internal ModuleInfo(TypeInfo source, CommandService service)
{
Source = source;
Service = service;
Name = source.Name;
_builder = ReflectionUtils.CreateBuilder<ModuleBase>(source, Service);

var groupAttr = source.GetCustomAttribute<GroupAttribute>();
if (groupAttr != null)
Prefix = groupAttr.Prefix;
else
Prefix = "";

var nameAttr = source.GetCustomAttribute<NameAttribute>();
if (nameAttr != null)
Name = nameAttr.Text;

var summaryAttr = source.GetCustomAttribute<SummaryAttribute>();
if (summaryAttr != null)
Summary = summaryAttr.Text;

var remarksAttr = source.GetCustomAttribute<RemarksAttribute>();
if (remarksAttr != null)
Remarks = remarksAttr.Text;

List<CommandInfo> commands = new List<CommandInfo>();
SearchClass(source, commands, Prefix);
Commands = commands;

Preconditions = Source.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
}
private void SearchClass(TypeInfo parentType, List<CommandInfo> commands, string groupPrefix)
{
foreach (var method in parentType.DeclaredMethods)
{
var cmdAttr = method.GetCustomAttribute<CommandAttribute>();
if (cmdAttr != null)
commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix));
}
foreach (var type in parentType.DeclaredNestedTypes)
{
var groupAttrib = type.GetCustomAttribute<GroupAttribute>();
if (groupAttrib != null)
{
string nextGroupPrefix;

if (groupPrefix != "")
nextGroupPrefix = groupPrefix + " " + (groupAttrib.Prefix ?? type.Name.ToLowerInvariant());
else
nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant();

SearchClass(type, commands, nextGroupPrefix);
}
}
}

internal ModuleBase CreateInstance(IDependencyMap map)
=> _builder(map);

public override string ToString() => Name;
private string DebuggerDisplay => Name;
}
}

+ 7
- 5
src/Discord.Net.Commands/Readers/EnumTypeReader.cs View File

@@ -31,11 +31,13 @@ namespace Discord.Commands


var byNameBuilder = ImmutableDictionary.CreateBuilder<string, object>(); var byNameBuilder = ImmutableDictionary.CreateBuilder<string, object>();
var byValueBuilder = ImmutableDictionary.CreateBuilder<T, object>(); var byValueBuilder = ImmutableDictionary.CreateBuilder<T, object>();
foreach (var v in Enum.GetValues(_enumType))
{
byNameBuilder.Add(v.ToString().ToLower(), v);
byValueBuilder.Add((T)v, v);

foreach (var v in Enum.GetNames(_enumType))
{
var parsedValue = Enum.Parse(_enumType, v);
byNameBuilder.Add(v.ToLower(), parsedValue);
if (!byValueBuilder.ContainsKey((T)parsedValue))
byValueBuilder.Add((T)parsedValue, parsedValue);
} }


_enumsByName = byNameBuilder.ToImmutable(); _enumsByName = byNameBuilder.ToImmutable();


+ 1
- 0
src/Discord.Net.Commands/RunMode.cs View File

@@ -2,6 +2,7 @@
{ {
public enum RunMode public enum RunMode
{ {
Default,
Sync, Sync,
Mixed, Mixed,
Async Async


src/Discord.Net.Commands/ReflectionUtils.cs → src/Discord.Net.Commands/Utilities/ReflectionUtils.cs View File

@@ -18,7 +18,7 @@ namespace Discord.Commands
throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\"");


var constructor = constructors[0]; var constructor = constructors[0];
ParameterInfo[] parameters = constructor.GetParameters();
System.Reflection.ParameterInfo[] parameters = constructor.GetParameters();


return (map) => return (map) =>
{ {

+ 2
- 2
src/Discord.Net.Commands/project.json View File

@@ -1,5 +1,5 @@
{ {
"version": "1.0.0-beta2-*",
"version": "1.0.0-*",
"description": "A Discord.Net extension adding support for bot commands.", "description": "A Discord.Net extension adding support for bot commands.",
"authors": [ "RogueException" ], "authors": [ "RogueException" ],


@@ -40,4 +40,4 @@
] ]
} }
} }
}
}

+ 15
- 0
src/Discord.Net.Core/API/Common/Embed.cs View File

@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using Newtonsoft.Json; using Newtonsoft.Json;


namespace Discord.API namespace Discord.API
@@ -13,9 +14,23 @@ namespace Discord.API
public string Description { get; set; } public string Description { get; set; }
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }
[JsonProperty("color")]
public uint? Color { get; set; }
[JsonProperty("timestamp")]
public DateTimeOffset? Timestamp { get; set; }
[JsonProperty("author")]
public Optional<EmbedAuthor> Author { get; set; }
[JsonProperty("footer")]
public Optional<EmbedFooter> Footer { get; set; }
[JsonProperty("video")]
public Optional<EmbedVideo> Video { get; set; }
[JsonProperty("thumbnail")] [JsonProperty("thumbnail")]
public Optional<EmbedThumbnail> Thumbnail { get; set; } public Optional<EmbedThumbnail> Thumbnail { get; set; }
[JsonProperty("image")]
public Optional<EmbedImage> Image { get; set; }
[JsonProperty("provider")] [JsonProperty("provider")]
public Optional<EmbedProvider> Provider { get; set; } public Optional<EmbedProvider> Provider { get; set; }
[JsonProperty("fields")]
public Optional<EmbedField[]> Fields { get; set; }
} }
} }

+ 16
- 0
src/Discord.Net.Core/API/Common/EmbedAuthor.cs View File

@@ -0,0 +1,16 @@
using Newtonsoft.Json;

namespace Discord.API
{
public class EmbedAuthor
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("icon_url")]
public string IconUrl { get; set; }
[JsonProperty("proxy_icon_url")]
public string ProxyIconUrl { get; set; }
}
}

+ 14
- 0
src/Discord.Net.Core/API/Common/EmbedField.cs View File

@@ -0,0 +1,14 @@
using Newtonsoft.Json;

namespace Discord.API
{
public class EmbedField
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
[JsonProperty("inline")]
public bool Inline { get; set; }
}
}

+ 14
- 0
src/Discord.Net.Core/API/Common/EmbedFooter.cs View File

@@ -0,0 +1,14 @@
using Newtonsoft.Json;

namespace Discord.API
{
public class EmbedFooter
{
[JsonProperty("text")]
public string Text { get; set; }
[JsonProperty("icon_url")]
public string IconUrl { get; set; }
[JsonProperty("proxy_icon_url")]
public string ProxyIconUrl { get; set; }
}
}

+ 17
- 0
src/Discord.Net.Core/API/Common/EmbedImage.cs View File

@@ -0,0 +1,17 @@
#pragma warning disable CS1591
using Newtonsoft.Json;

namespace Discord.API
{
public class EmbedImage
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("proxy_url")]
public string ProxyUrl { get; set; }
[JsonProperty("height")]
public Optional<int> Height { get; set; }
[JsonProperty("width")]
public Optional<int> Width { get; set; }
}
}

+ 15
- 0
src/Discord.Net.Core/API/Common/EmbedVideo.cs View File

@@ -0,0 +1,15 @@
#pragma warning disable CS1591
using Newtonsoft.Json;

namespace Discord.API
{
public class EmbedVideo
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("height")]
public Optional<int> Height { get; set; }
[JsonProperty("width")]
public Optional<int> Width { get; set; }
}
}

+ 41
- 51
src/Discord.Net.Core/API/DiscordRestApiClient.cs View File

@@ -49,7 +49,7 @@ namespace Discord.API
{ {
_restClientProvider = restClientProvider; _restClientProvider = restClientProvider;
_userAgent = userAgent; _userAgent = userAgent;
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
_serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() };
RequestQueue = requestQueue; RequestQueue = requestQueue;
FetchCurrentUser = true; FetchCurrentUser = true;


@@ -165,30 +165,30 @@ namespace Discord.API


//Core //Core
internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids, internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids,
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null)
=> SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
=> SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
public async Task SendAsync(string method, string endpoint, public async Task SendAsync(string method, string endpoint,
string bucketId = null, string clientBucketId = null, RequestOptions options = null)
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
{ {
options = options ?? new RequestOptions(); options = options ?? new RequestOptions();
options.HeaderOnly = true; options.HeaderOnly = true;
options.BucketId = bucketId;
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
options.IsClientBucket = AuthTokenType == TokenType.User;


var request = new RestRequest(_restClient, method, endpoint, options); var request = new RestRequest(_restClient, method, endpoint, options);
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false);
} }


internal Task SendJsonAsync(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids, internal Task SendJsonAsync(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids,
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null)
=> SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
=> SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
public async Task SendJsonAsync(string method, string endpoint, object payload, public async Task SendJsonAsync(string method, string endpoint, object payload,
string bucketId = null, string clientBucketId = null, RequestOptions options = null)
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
{ {
options = options ?? new RequestOptions(); options = options ?? new RequestOptions();
options.HeaderOnly = true; options.HeaderOnly = true;
options.BucketId = bucketId;
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
options.IsClientBucket = AuthTokenType == TokenType.User;


var json = payload != null ? SerializeJson(payload) : null; var json = payload != null ? SerializeJson(payload) : null;
var request = new JsonRestRequest(_restClient, method, endpoint, json, options); var request = new JsonRestRequest(_restClient, method, endpoint, json, options);
@@ -196,43 +196,43 @@ namespace Discord.API
} }


internal Task SendMultipartAsync(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, internal Task SendMultipartAsync(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids,
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null)
=> SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
=> SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs,
string bucketId = null, string clientBucketId = null, RequestOptions options = null)
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
{ {
options = options ?? new RequestOptions(); options = options ?? new RequestOptions();
options.HeaderOnly = true; options.HeaderOnly = true;
options.BucketId = bucketId;
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
options.IsClientBucket = AuthTokenType == TokenType.User;


var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options);
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false);
} }


internal Task<TResponse> SendAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, BucketIds ids, internal Task<TResponse> SendAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, BucketIds ids,
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class
=> SendAsync<TResponse>(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class
=> SendAsync<TResponse>(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint, public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint,
string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class
{ {
options = options ?? new RequestOptions(); options = options ?? new RequestOptions();
options.BucketId = bucketId;
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
options.IsClientBucket = AuthTokenType == TokenType.User;


var request = new RestRequest(_restClient, method, endpoint, options); var request = new RestRequest(_restClient, method, endpoint, options);
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false));
} }


internal Task<TResponse> SendJsonAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids, internal Task<TResponse> SendJsonAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids,
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class
=> SendJsonAsync<TResponse>(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class
=> SendJsonAsync<TResponse>(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
public async Task<TResponse> SendJsonAsync<TResponse>(string method, string endpoint, object payload, public async Task<TResponse> SendJsonAsync<TResponse>(string method, string endpoint, object payload,
string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class
{ {
options = options ?? new RequestOptions(); options = options ?? new RequestOptions();
options.BucketId = bucketId;
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
options.IsClientBucket = AuthTokenType == TokenType.User;


var json = payload != null ? SerializeJson(payload) : null; var json = payload != null ? SerializeJson(payload) : null;
var request = new JsonRestRequest(_restClient, method, endpoint, json, options); var request = new JsonRestRequest(_restClient, method, endpoint, json, options);
@@ -240,14 +240,14 @@ namespace Discord.API
} }


internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids,
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null)
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs,
string bucketId = null, string clientBucketId = null, RequestOptions options = null)
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
{ {
options = options ?? new RequestOptions(); options = options ?? new RequestOptions();
options.BucketId = bucketId;
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
options.IsClientBucket = AuthTokenType == TokenType.User;


var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options);
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false));
@@ -432,20 +432,22 @@ namespace Discord.API
if (relativeId != null) if (relativeId != null)
endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}";
else else
endpoint = () =>$"channels/{channelId}/messages?limit={limit}";
endpoint = () => $"channels/{channelId}/messages?limit={limit}";
return await SendAsync<IReadOnlyCollection<Message>>("GET", endpoint, ids, options: options).ConfigureAwait(false); return await SendAsync<IReadOnlyCollection<Message>>("GET", endpoint, ids, options: options).ConfigureAwait(false);
} }
public async Task<Message> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) public async Task<Message> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null)
{ {
Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotNull(args, nameof(args)); Preconditions.NotNull(args, nameof(args));
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content));
if (!args.Embed.IsSpecified || args.Embed.Value == null)
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content));

if (args.Content.Length > DiscordConfig.MaxMessageSize) if (args.Content.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content));
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);


var ids = new BucketIds(channelId: channelId); var ids = new BucketIds(channelId: channelId);
return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", args, ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false);
return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
} }
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null)
{ {
@@ -464,7 +466,7 @@ namespace Discord.API
} }


var ids = new BucketIds(channelId: channelId); var ids = new BucketIds(channelId: channelId);
return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false);
return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
} }
public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
{ {
@@ -496,21 +498,22 @@ namespace Discord.API
break; break;
} }
} }
public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null)
public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null)
{ {
Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotEqual(messageId, 0, nameof(messageId)); Preconditions.NotEqual(messageId, 0, nameof(messageId));
Preconditions.NotNull(args, nameof(args)); Preconditions.NotNull(args, nameof(args));
if (args.Content.IsSpecified) if (args.Content.IsSpecified)
{ {
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content));
if (!args.Embed.IsSpecified)
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content));
if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) if (args.Content.Value.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content));
} }
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);


var ids = new BucketIds(channelId: channelId); var ids = new BucketIds(channelId: channelId);
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false);
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
} }
public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null)
{ {
@@ -1112,19 +1115,6 @@ namespace Discord.API
using (JsonReader reader = new JsonTextReader(text)) using (JsonReader reader = new JsonTextReader(text))
return _serializer.Deserialize<T>(reader); return _serializer.Deserialize<T>(reader);
} }
internal string GetBucketId(ulong guildId = 0, ulong channelId = 0, [CallerMemberName] string methodName = "")
{
if (guildId != 0)
{
if (channelId != 0)
return $"{methodName}({guildId}/{channelId})";
else
return $"{methodName}({guildId})";
}
else if (channelId != 0)
return $"{methodName}({channelId})";
return $"{methodName}()";
}


internal class BucketIds internal class BucketIds
{ {


+ 3
- 0
src/Discord.Net.Core/API/Rest/CreateMessageParams.cs View File

@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using Newtonsoft.Json; using Newtonsoft.Json;


namespace Discord.API.Rest namespace Discord.API.Rest
@@ -13,6 +14,8 @@ namespace Discord.API.Rest
public Optional<string> Nonce { get; set; } public Optional<string> Nonce { get; set; }
[JsonProperty("tts")] [JsonProperty("tts")]
public Optional<bool> IsTTS { get; set; } public Optional<bool> IsTTS { get; set; }
[JsonProperty("embed")]
public Optional<Embed> Embed { get; set; }


public CreateMessageParams(string content) public CreateMessageParams(string content)
{ {


+ 2
- 0
src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs View File

@@ -8,5 +8,7 @@ namespace Discord.API.Rest
{ {
[JsonProperty("content")] [JsonProperty("content")]
public Optional<string> Content { get; set; } public Optional<string> Content { get; set; }
[JsonProperty("embed")]
public Optional<Embed> Embed { get; set; }
} }
} }

+ 60
- 0
src/Discord.Net.Core/Discord.Net.Core.csproj View File

@@ -0,0 +1,60 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Description>A .Net API wrapper and bot framework for Discord.</Description>
<VersionPrefix>1.0.0-beta2</VersionPrefix>
<TargetFramework>netstandard1.3</TargetFramework>
<AssemblyName>Discord.Net.Core</AssemblyName>
<PackageTags>discord;discordapp</PackageTags>
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
<EmbeddedResource Include="compiler\resources\**\*" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161104-2</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Win32.Primitives">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>9.0.1</Version>
</PackageReference>
<PackageReference Include="System.Collections.Concurrent">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Collections.Immutable">
<Version>1.3.0</Version>
</PackageReference>
<PackageReference Include="System.Interactive.Async">
<Version>3.1.0</Version>
</PackageReference>
<PackageReference Include="System.Net.Http">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Net.WebSockets.Client">
<Version>4.3.0</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Label="Configuration">
<SignAssembly>False</SignAssembly>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
<WarningsAsErrors>true</WarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

+ 0
- 19
src/Discord.Net.Core/Discord.Net.Core.xproj View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>91e9e7bd-75c9-4e98-84aa-2c271922e5c2</ProjectGuid>
<RootNamespace>Discord</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

+ 2
- 1
src/Discord.Net.Core/DiscordConfig.cs View File

@@ -11,9 +11,10 @@ namespace Discord
"Unknown"; "Unknown";


public static readonly string ClientAPIUrl = $"https://discordapp.com/api/v{APIVersion}/"; public static readonly string ClientAPIUrl = $"https://discordapp.com/api/v{APIVersion}/";
public const string CDNUrl = "https://discordcdn.com/";
public const string CDNUrl = "https://cdn.discordapp.com/";
public const string InviteUrl = "https://discord.gg/"; public const string InviteUrl = "https://discord.gg/";


public const int DefaultRequestTimeout = 15000;
public const int MaxMessageSize = 2000; public const int MaxMessageSize = 2000;
public const int MaxMessagesPerBatch = 100; public const int MaxMessagesPerBatch = 100;
public const int MaxUsersPerBatch = 1000; public const int MaxUsersPerBatch = 1000;


+ 1
- 1
src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs View File

@@ -8,7 +8,7 @@ namespace Discord
public interface IMessageChannel : IChannel public interface IMessageChannel : IChannel
{ {
/// <summary> Sends a message to this message channel. </summary> /// <summary> Sends a message to this message channel. </summary>
Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null);
Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null);
/// <summary> Sends a file to this text channel, with an optional caption. </summary> /// <summary> Sends a file to this text channel, with an optional caption. </summary>
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null);
/// <summary> Sends a file to this text channel, with an optional caption. </summary> /// <summary> Sends a file to this text channel, with an optional caption. </summary>


+ 29
- 0
src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs View File

@@ -0,0 +1,29 @@
using System.Diagnostics;
using Model = Discord.API.EmbedAuthor;

namespace Discord
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedAuthor
{
public string Name { get; set; }
public string Url { get; set; }
public string IconUrl { get; set; }
public string ProxyIconUrl { get; set; }

private EmbedAuthor(string name, string url, string iconUrl, string proxyIconUrl)
{
Name = name;
Url = url;
IconUrl = iconUrl;
ProxyIconUrl = proxyIconUrl;
}
internal static EmbedAuthor Create(Model model)
{
return new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl);
}

private string DebuggerDisplay => $"{Name} ({Url})";
public override string ToString() => Name;
}
}

+ 208
- 0
src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs View File

@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using Embed = Discord.API.Embed;
using Field = Discord.API.EmbedField;
using Author = Discord.API.EmbedAuthor;
using Footer = Discord.API.EmbedFooter;
using Thumbnail = Discord.API.EmbedThumbnail;
using Image = Discord.API.EmbedImage;

namespace Discord
{
public class EmbedBuilder
{
private readonly Embed _model;
private readonly List<Field> _fields;

public EmbedBuilder()
{
_model = new Embed { Type = "rich" };
_fields = new List<Field>();
}

public string Title { get { return _model.Title; } set { _model.Title = value; } }
public string Description { get { return _model.Description; } set { _model.Description = value; } }
public string Url { get { return _model.Url; } set { _model.Url = value; } }
public string ThumbnailUrl { get; set; }
public string ImageUrl { get; set; }
public DateTimeOffset? Timestamp { get; set; }
public Color? Color { get { return _model.Color.HasValue ? new Color(_model.Color.Value) : (Color?)null; } set { _model.Color = value?.RawValue; } }
public EmbedAuthorBuilder Author { get; set; }
public EmbedFooterBuilder Footer { get; set; }

public EmbedBuilder WithTitle(string title)
{
Title = title;
return this;
}
public EmbedBuilder WithDescription(string description)
{
Description = description;
return this;
}
public EmbedBuilder WithUrl(string url)
{
Url = url;
return this;
}
public EmbedBuilder WithThumbnailUrl(string thumbnailUrl)
{
ThumbnailUrl = thumbnailUrl;
return this;
}
public EmbedBuilder WithImageUrl(string imageUrl)
{
ImageUrl = ImageUrl;
return this;
}
public EmbedBuilder WithCurrentTimestamp()
{
Timestamp = DateTimeOffset.UtcNow;
return this;
}
public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset)
{
Timestamp = dateTimeOffset;
return this;
}
public EmbedBuilder WithColor(Color color)
{
Color = color;
return this;
}

public EmbedBuilder WithAuthor(EmbedAuthorBuilder author)
{
Author = author;
return this;
}
public EmbedBuilder WithAuthor(Action<EmbedAuthorBuilder> action)
{
var author = new EmbedAuthorBuilder();
action(author);
Author = author;
return this;
}
public EmbedBuilder WithFooter(EmbedFooterBuilder footer)
{
Footer = footer;
return this;
}
public EmbedBuilder WithFooter(Action<EmbedFooterBuilder> action)
{
var footer = new EmbedFooterBuilder();
action(footer);
Footer = footer;
return this;
}

public EmbedBuilder AddField(Action<EmbedFieldBuilder> action)
{
var field = new EmbedFieldBuilder();
action(field);
_fields.Add(field.ToModel());
return this;
}

internal Embed Build()
{
_model.Author = Author?.ToModel();
_model.Footer = Footer?.ToModel();
_model.Timestamp = Timestamp?.ToUniversalTime();
_model.Thumbnail = ThumbnailUrl != null ? new Thumbnail { Url = ThumbnailUrl } : null;
_model.Image = ImageUrl != null ? new Image { Url = ImageUrl } : null;
_model.Fields = _fields.ToArray();
return _model;
}
}

public class EmbedFieldBuilder
{
private readonly Field _model;

public string Name { get { return _model.Name; } set { _model.Name = value; } }
public string Value { get { return _model.Value; } set { _model.Value = value; } }
public bool IsInline { get { return _model.Inline; } set { _model.Inline = value; } }

public EmbedFieldBuilder()
{
_model = new Field();
}

public EmbedFieldBuilder WithName(string name)
{
Name = name;
return this;
}
public EmbedFieldBuilder WithValue(string value)
{
Value = value;
return this;
}
public EmbedFieldBuilder WithIsInline(bool isInline)
{
IsInline = isInline;
return this;
}

internal Field ToModel() => _model;
}

public class EmbedAuthorBuilder
{
private readonly Author _model;

public string Name { get { return _model.Name; } set { _model.Name = value; } }
public string Url { get { return _model.Url; } set { _model.Url = value; } }
public string IconUrl { get { return _model.IconUrl; } set { _model.IconUrl = value; } }

public EmbedAuthorBuilder()
{
_model = new Author();
}

public EmbedAuthorBuilder WithName(string name)
{
Name = name;
return this;
}
public EmbedAuthorBuilder WithUrl(string url)
{
Url = url;
return this;
}
public EmbedAuthorBuilder WithIconUrl(string iconUrl)
{
IconUrl = iconUrl;
return this;
}

internal Author ToModel() => _model;
}

public class EmbedFooterBuilder
{
private readonly Footer _model;

public string Text { get { return _model.Text; } set { _model.Text = value; } }
public string IconUrl { get { return _model.IconUrl; } set { _model.IconUrl = value; } }

public EmbedFooterBuilder()
{
_model = new Footer();
}

public EmbedFooterBuilder WithText(string text)
{
Text = text;
return this;
}
public EmbedFooterBuilder WithIconUrl(string iconUrl)
{
IconUrl = iconUrl;
return this;
}

internal Footer ToModel() => _model;
}
}

+ 27
- 0
src/Discord.Net.Core/Entities/Messages/EmbedField.cs View File

@@ -0,0 +1,27 @@
using System.Diagnostics;
using Model = Discord.API.EmbedField;

namespace Discord
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedField
{
public string Name { get; set; }
public string Value { get; set; }
public bool Inline { get; set; }

private EmbedField(string name, string value, bool inline)
{
Name = name;
Value = value;
Inline = inline;
}
internal static EmbedField Create(Model model)
{
return new EmbedField(model.Name, model.Value, model.Inline);
}

private string DebuggerDisplay => $"{Name} ({Value}";
public override string ToString() => Name;
}
}

+ 27
- 0
src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs View File

@@ -0,0 +1,27 @@
using System.Diagnostics;
using Model = Discord.API.EmbedFooter;

namespace Discord
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedFooter
{
public string Text { get; set; }
public string IconUrl { get; set; }
public string ProxyUrl { get; set; }

private EmbedFooter(string text, string iconUrl, string proxyUrl)
{
Text = text;
IconUrl = iconUrl;
ProxyUrl = proxyUrl;
}
internal static EmbedFooter Create(Model model)
{
return new EmbedFooter(model.Text, model.IconUrl, model.ProxyIconUrl);
}

private string DebuggerDisplay => $"{Text} ({IconUrl})";
public override string ToString() => Text;
}
}

+ 31
- 0
src/Discord.Net.Core/Entities/Messages/EmbedImage.cs View File

@@ -0,0 +1,31 @@
using System.Diagnostics;
using Model = Discord.API.EmbedImage;

namespace Discord
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedImage
{
public string Url { get; }
public string ProxyUrl { get; }
public int? Height { get; }
public int? Width { get; }

private EmbedImage(string url, string proxyUrl, int? height, int? width)
{
Url = url;
ProxyUrl = proxyUrl;
Height = height;
Width = width;
}
internal static EmbedImage Create(Model model)
{
return new EmbedImage(model.Url, model.ProxyUrl,
model.Height.IsSpecified ? model.Height.Value : (int?)null,
model.Width.IsSpecified ? model.Width.Value : (int?)null);
}

private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})";
public override string ToString() => Url;
}
}

+ 2
- 2
src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs View File

@@ -25,7 +25,7 @@ namespace Discord
model.Width.IsSpecified ? model.Width.Value : (int?)null); model.Width.IsSpecified ? model.Width.Value : (int?)null);
} }


private string DebuggerDisplay => $"{ToString()} ({Url})";
public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0";
private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})";
public override string ToString() => Url;
} }
} }

+ 29
- 0
src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs View File

@@ -0,0 +1,29 @@
using System.Diagnostics;
using Model = Discord.API.EmbedVideo;

namespace Discord
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedVideo
{
public string Url { get; }
public int? Height { get; }
public int? Width { get; }

private EmbedVideo(string url, int? height, int? width)
{
Url = url;
Height = height;
Width = width;
}
internal static EmbedVideo Create(Model model)
{
return new EmbedVideo(model.Url,
model.Height.IsSpecified ? model.Height.Value : (int?)null,
model.Width.IsSpecified ? model.Width.Value : (int?)null);
}

private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})";
public override string ToString() => Url;
}
}

+ 11
- 1
src/Discord.Net.Core/Entities/Messages/IEmbed.cs View File

@@ -1,4 +1,7 @@
namespace Discord
using System;
using System.Collections.Immutable;

namespace Discord
{ {
public interface IEmbed public interface IEmbed
{ {
@@ -6,7 +9,14 @@
string Type { get; } string Type { get; }
string Title { get; } string Title { get; }
string Description { get; } string Description { get; }
DateTimeOffset? Timestamp { get; }
Color? Color { get; }
EmbedImage? Image { get; }
EmbedVideo? Video { get; }
EmbedAuthor? Author { get; }
EmbedFooter? Footer { get; }
EmbedProvider? Provider { get; } EmbedProvider? Provider { get; }
EmbedThumbnail? Thumbnail { get; } EmbedThumbnail? Thumbnail { get; }
ImmutableArray<EmbedField> Fields { get; }
} }
} }

+ 1
- 2
src/Discord.Net.Core/Entities/Messages/IUserMessage.cs View File

@@ -1,5 +1,4 @@
using Discord.API.Rest;
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;




+ 12
- 0
src/Discord.Net.Core/Entities/Messages/ModifyMessageParams.cs View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Discord
{
public class ModifyMessageParams
{
public Optional<string> Content { get; set; }
public Optional<EmbedBuilder> Embed { get; set; }
}
}

+ 7
- 5
src/Discord.Net.Core/Entities/Messages/TagHandling.cs View File

@@ -2,10 +2,12 @@
{ {
public enum TagHandling public enum TagHandling
{ {
Ignore = 0,
Remove,
Name,
FullName,
Sanitize
Ignore = 0, //<@53905483156684800> -> <@53905483156684800>
Remove, //<@53905483156684800> ->
Name, //<@53905483156684800> -> @Voltana
NameNoPrefix, //<@53905483156684800> -> Voltana
FullName, //<@53905483156684800> -> @Voltana#8252
FullNameNoPrefix, //<@53905483156684800> -> Voltana#8252
Sanitize //<@53905483156684800> -> <@53905483156684800> (w/ nbsp)
} }
} }

+ 6
- 0
src/Discord.Net.Core/Entities/Roles/Color.cs View File

@@ -32,6 +32,12 @@ namespace Discord
} }
public Color(float r, float g, float b) public Color(float r, float g, float b)
{ {
if (r < 0.0f || r > 1.0f)
throw new ArgumentOutOfRangeException(nameof(r), "A float value must be within [0,1]");
if (g < 0.0f || g > 1.0f)
throw new ArgumentOutOfRangeException(nameof(g), "A float value must be within [0,1]");
if (b < 0.0f || b > 1.0f)
throw new ArgumentOutOfRangeException(nameof(b), "A float value must be within [0,1]");
RawValue = RawValue =
((uint)(r * 255.0f) << 16) | ((uint)(r * 255.0f) << 16) |
((uint)(g * 255.0f) << 8) | ((uint)(g * 255.0f) << 8) |


+ 2
- 2
src/Discord.Net.Core/Entities/Roles/IRole.cs View File

@@ -4,7 +4,7 @@ using System.Threading.Tasks;


namespace Discord namespace Discord
{ {
public interface IRole : ISnowflakeEntity, IDeletable, IMentionable
public interface IRole : ISnowflakeEntity, IDeletable, IMentionable, IComparable<IRole>
{ {
/// <summary> Gets the guild owning this role.</summary> /// <summary> Gets the guild owning this role.</summary>
IGuild Guild { get; } IGuild Guild { get; }
@@ -27,4 +27,4 @@ namespace Discord
///// <summary> Modifies this role. </summary> ///// <summary> Modifies this role. </summary>
Task ModifyAsync(Action<ModifyGuildRoleParams> func, RequestOptions options = null); Task ModifyAsync(Action<ModifyGuildRoleParams> func, RequestOptions options = null);
} }
}
}

+ 14
- 5
src/Discord.Net.Core/Extensions/GuildUserExtensions.cs View File

@@ -6,14 +6,23 @@ namespace Discord
{ {
public static class GuildUserExtensions public static class GuildUserExtensions
{ {
//TODO: Should we remove Add/Remove? Encourages race conditions.
public static Task AddRolesAsync(this IGuildUser user, params IRole[] roles) public static Task AddRolesAsync(this IGuildUser user, params IRole[] roles)
=> AddRolesAsync(user, (IEnumerable<IRole>)roles);
=> ChangeRolesAsync(user, add: roles);
public static Task AddRolesAsync(this IGuildUser user, IEnumerable<IRole> roles) public static Task AddRolesAsync(this IGuildUser user, IEnumerable<IRole> roles)
=> user.ModifyAsync(x => x.RoleIds = user.RoleIds.Concat(roles.Select(y => y.Id)).ToArray());

=> ChangeRolesAsync(user, add: roles);
public static Task RemoveRolesAsync(this IGuildUser user, params IRole[] roles) public static Task RemoveRolesAsync(this IGuildUser user, params IRole[] roles)
=> RemoveRolesAsync(user, (IEnumerable<IRole>)roles);
=> ChangeRolesAsync(user, remove: roles);
public static Task RemoveRolesAsync(this IGuildUser user, IEnumerable<IRole> roles) public static Task RemoveRolesAsync(this IGuildUser user, IEnumerable<IRole> roles)
=> user.ModifyAsync(x => x.RoleIds = user.RoleIds.Except(roles.Select(y => y.Id)).ToArray());
=> ChangeRolesAsync(user, remove: roles);
public static async Task ChangeRolesAsync(this IGuildUser user, IEnumerable<IRole> add = null, IEnumerable<IRole> remove = null)
{
IEnumerable<ulong> roleIds = user.RoleIds;
if (remove != null)
roleIds = roleIds.Except(remove.Select(x => x.Id));
if (add != null)
roleIds = roleIds.Concat(add.Select(x => x.Id));
await user.ModifyAsync(x => x.RoleIds = roleIds.ToArray()).ConfigureAwait(false);
}
} }
} }

+ 18
- 0
src/Discord.Net.Core/Extensions/RoleExtensions.cs View File

@@ -0,0 +1,18 @@
namespace Discord
{
internal static class RoleExtensions
{
internal static int Compare(this IRole left, IRole right)
{
if (left == null)
return -1;
if (right == null)
return 1;
var result = left.Position.CompareTo(right.Position);
// As per Discord's documentation, a tie is broken by ID
if (result != 0)
return result;
return left.Id.CompareTo(right.Id);
}
}
}

+ 31
- 9
src/Discord.Net.Core/Net/Queue/ClientBucket.cs View File

@@ -2,25 +2,47 @@


namespace Discord.Net.Queue namespace Discord.Net.Queue
{ {
public struct ClientBucket
public enum ClientBucketType
{ {
public const string SendEditId = "<send_edit>";
Unbucketed = 0,
SendEdit = 1
}
internal struct ClientBucket
{
private static readonly ImmutableDictionary<ClientBucketType, ClientBucket> _defsByType;
private static readonly ImmutableDictionary<string, ClientBucket> _defsById;


private static readonly ImmutableDictionary<string, ClientBucket> _defs;
static ClientBucket() static ClientBucket()
{ {
var builder = ImmutableDictionary.CreateBuilder<string, ClientBucket>();
builder.Add(SendEditId, new ClientBucket(10, 10));
_defs = builder.ToImmutable();
}
var buckets = new[]
{
new ClientBucket(ClientBucketType.Unbucketed, "<unbucketed>", 10, 10),
new ClientBucket(ClientBucketType.SendEdit, "<send_edit>", 10, 10)
};


public static ClientBucket Get(string id) =>_defs[id];
var builder = ImmutableDictionary.CreateBuilder<ClientBucketType, ClientBucket>();
foreach (var bucket in buckets)
builder.Add(bucket.Type, bucket);
_defsByType = builder.ToImmutable();

var builder2 = ImmutableDictionary.CreateBuilder<string, ClientBucket>();
foreach (var bucket in buckets)
builder2.Add(bucket.Id, bucket);
_defsById = builder2.ToImmutable();
}


public static ClientBucket Get(ClientBucketType type) => _defsByType[type];
public static ClientBucket Get(string id) => _defsById[id];
public ClientBucketType Type { get; }
public string Id { get; }
public int WindowCount { get; } public int WindowCount { get; }
public int WindowSeconds { get; } public int WindowSeconds { get; }


public ClientBucket(int count, int seconds)
public ClientBucket(ClientBucketType type, string id, int count, int seconds)
{ {
Type = type;
Id = id;
WindowCount = count; WindowCount = count;
WindowSeconds = seconds; WindowSeconds = seconds;
} }


+ 2
- 0
src/Discord.Net.Core/Net/Queue/RequestQueue.cs View File

@@ -79,7 +79,9 @@ namespace Discord.Net.Queue
int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds); int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds);
if (millis > 0) if (millis > 0)
{ {
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]"); Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]");
#endif
await Task.Delay(millis).ConfigureAwait(false); await Task.Delay(millis).ConfigureAwait(false);
} }
} }


+ 96
- 44
src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs View File

@@ -1,7 +1,10 @@
using Newtonsoft.Json;
using Discord.Net.Rest;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
#if DEBUG_LIMITS
using System.Diagnostics; using System.Diagnostics;
#endif
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
@@ -27,8 +30,8 @@ namespace Discord.Net.Queue


_lock = new object(); _lock = new object();


if (request.Options.ClientBucketId != null)
WindowCount = ClientBucket.Get(request.Options.ClientBucketId).WindowCount;
if (request.Options.IsClientBucket)
WindowCount = ClientBucket.Get(request.Options.BucketId).WindowCount;
else else
WindowCount = 1; //Only allow one request until we get a header back WindowCount = 1; //Only allow one request until we get a header back
_semaphore = WindowCount; _semaphore = WindowCount;
@@ -40,62 +43,91 @@ namespace Discord.Net.Queue
public async Task<Stream> SendAsync(RestRequest request) public async Task<Stream> SendAsync(RestRequest request)
{ {
int id = Interlocked.Increment(ref nextId); int id = Interlocked.Increment(ref nextId);
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Start"); Debug.WriteLine($"[{id}] Start");
#endif
LastAttemptAt = DateTimeOffset.UtcNow; LastAttemptAt = DateTimeOffset.UtcNow;
while (true) while (true)
{ {
await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false); await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false);
await EnterAsync(id, request).ConfigureAwait(false); await EnterAsync(id, request).ConfigureAwait(false);


#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Sending..."); Debug.WriteLine($"[{id}] Sending...");
var response = await request.SendAsync().ConfigureAwait(false);
TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]);
var info = new RateLimitInfo(response.Headers);

if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300)
#endif
TimeSpan lag = default(TimeSpan);
RateLimitInfo info = default(RateLimitInfo);
try
{ {
switch (response.StatusCode)
var response = await request.SendAsync().ConfigureAwait(false);
lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]);
info = new RateLimitInfo(response.Headers);

if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300)
{ {
case (HttpStatusCode)429:
if (info.IsGlobal)
{
Debug.WriteLine($"[{id}] (!) 429 [Global]");
_queue.PauseGlobal(info, lag);
}
else
{
Debug.WriteLine($"[{id}] (!) 429");
UpdateRateLimit(id, request, info, lag, true);
}
await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false);
continue; //Retry
case HttpStatusCode.BadGateway: //502
Debug.WriteLine($"[{id}] (!) 502");
continue; //Continue
default:
string reason = null;
if (response.Stream != null)
{
try
switch (response.StatusCode)
{
case (HttpStatusCode)429:
if (info.IsGlobal)
{
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] (!) 429 [Global]");
#endif
_queue.PauseGlobal(info, lag);
}
else
{
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] (!) 429");
#endif
UpdateRateLimit(id, request, info, lag, true);
}
await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false);
continue; //Retry
case HttpStatusCode.BadGateway: //502
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] (!) 502");
#endif
continue; //Continue
default:
string reason = null;
if (response.Stream != null)
{ {
using (var reader = new StreamReader(response.Stream))
using (var jsonReader = new JsonTextReader(reader))
try
{ {
var json = JToken.Load(jsonReader);
reason = json.Value<string>("message");
using (var reader = new StreamReader(response.Stream))
using (var jsonReader = new JsonTextReader(reader))
{
var json = JToken.Load(jsonReader);
reason = json.Value<string>("message");
}
} }
catch { }
} }
catch { }
}
throw new HttpException(response.StatusCode, reason);
throw new HttpException(response.StatusCode, reason);
}
}
else
{
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Success");
#endif
return response.Stream;
} }
} }
else
#if DEBUG_LIMITS
catch
{
Debug.WriteLine($"[{id}] Error");
throw;
}
#endif
finally
{ {
Debug.WriteLine($"[{id}] Success");
UpdateRateLimit(id, request, info, lag, false); UpdateRateLimit(id, request, info, lag, false);
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Stop"); Debug.WriteLine($"[{id}] Stop");
return response.Stream;
#endif
} }
} }
} }
@@ -135,7 +167,9 @@ namespace Discord.Net.Queue
if (resetAt > timeoutAt) if (resetAt > timeoutAt)
throw new RateLimitedException(); throw new RateLimitedException();
int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds);
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)");
#endif
if (millis > 0) if (millis > 0)
await Task.Delay(millis, request.CancelToken).ConfigureAwait(false); await Task.Delay(millis, request.CancelToken).ConfigureAwait(false);
} }
@@ -143,13 +177,17 @@ namespace Discord.Net.Queue
{ {
if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0) if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0)
throw new RateLimitedException(); throw new RateLimitedException();
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)"); Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)");
#endif
await Task.Delay(500, request.CancelToken).ConfigureAwait(false); await Task.Delay(500, request.CancelToken).ConfigureAwait(false);
} }
continue; continue;
} }
#if DEBUG_LIMITS
else else
Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)"); Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)");
#endif
break; break;
} }
} }
@@ -166,7 +204,9 @@ namespace Discord.Net.Queue
{ {
WindowCount = info.Limit.Value; WindowCount = info.Limit.Value;
_semaphore = info.Remaining.Value; _semaphore = info.Remaining.Value;
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}"); Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}");
#endif
} }


var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
@@ -182,24 +222,32 @@ namespace Discord.Net.Queue
{ {
//RetryAfter is more accurate than Reset, where available //RetryAfter is more accurate than Reset, where available
resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value); resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value);
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)");
#endif
} }
else if (info.Reset.HasValue) else if (info.Reset.HasValue)
{ {
resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds); resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds);
int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds;
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {lag.TotalMilliseconds} ms lag)"); Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {lag.TotalMilliseconds} ms lag)");
#endif
} }
else if (request.Options.ClientBucketId != null)
else if (request.Options.IsClientBucket && request.Options.BucketId != null)
{ {
resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds);
Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds * 1000} ms)");
resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.BucketId).WindowSeconds);
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.BucketId).WindowSeconds * 1000} ms)");
#endif
} }


if (resetTick == null) if (resetTick == null)
{ {
WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token) WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token)
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Disabled Semaphore"); Debug.WriteLine($"[{id}] Disabled Semaphore");
#endif
return; return;
} }


@@ -207,7 +255,9 @@ namespace Discord.Net.Queue
{ {
_resetTick = resetTick; _resetTick = resetTick;
LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms"); Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms");
#endif


if (!hasQueuedReset) if (!hasQueuedReset)
{ {
@@ -227,7 +277,9 @@ namespace Discord.Net.Queue
millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds); millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds);
if (millis <= 0) //Make sure we havent gotten a more accurate reset time if (millis <= 0) //Make sure we havent gotten a more accurate reset time
{ {
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] * Reset *"); Debug.WriteLine($"[{id}] * Reset *");
#endif
_semaphore = WindowCount; _semaphore = WindowCount;
_resetTick = null; _resetTick = null;
return; return;
@@ -236,4 +288,4 @@ namespace Discord.Net.Queue
} }
} }
} }
}
}

+ 1
- 1
src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs View File

@@ -120,7 +120,7 @@ namespace Discord.Net.Rest
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token; cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token;
HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false);
var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault());
var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null; var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null;


return new RestResponse(response.StatusCode, headers, stream); return new RestResponse(response.StatusCode, headers, stream);


+ 2
- 0
src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs View File

@@ -101,6 +101,8 @@ namespace Discord.Net.WebSockets


if (_client != null && _client.State == WebSocketState.Open) if (_client != null && _client.State == WebSocketState.Open)
{ {
var token = new CancellationToken();
await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", token);
_client.Dispose(); _client.Dispose();
_client = null; _client = null;
} }


+ 2
- 2
src/Discord.Net.Core/RequestOptions.cs View File

@@ -10,7 +10,7 @@


internal bool IgnoreState { get; set; } internal bool IgnoreState { get; set; }
internal string BucketId { get; set; } internal string BucketId { get; set; }
internal string ClientBucketId { get; set; }
internal bool IsClientBucket { get; set; }


internal static RequestOptions CreateOrClone(RequestOptions options) internal static RequestOptions CreateOrClone(RequestOptions options)
{ {
@@ -22,7 +22,7 @@


public RequestOptions() public RequestOptions()
{ {
Timeout = 30000;
Timeout = DiscordConfig.DefaultRequestTimeout;
} }


public RequestOptions Clone() => MemberwiseClone() as RequestOptions; public RequestOptions Clone() => MemberwiseClone() as RequestOptions;


+ 43
- 11
src/Discord.Net.Core/Utils/MentionUtils.cs View File

@@ -85,14 +85,17 @@ namespace Discord
return false; return false;
} }


internal static string Resolve(IMessage msg, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling)
internal static string Resolve(IMessage msg, int startIndex, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling)
{ {
var text = new StringBuilder(msg.Content);
var text = new StringBuilder(msg.Content.Substring(startIndex));
var tags = msg.Tags; var tags = msg.Tags;
int indexOffset = 0;
int indexOffset = -startIndex;


foreach (var tag in tags) foreach (var tag in tags)
{ {
if (tag.Index < startIndex)
continue;

string newText = ""; string newText = "";
switch (tag.Type) switch (tag.Type)
{ {
@@ -139,12 +142,22 @@ namespace Discord
if (user != null) if (user != null)
return $"@{guildUser?.Nickname ?? user?.Username}"; return $"@{guildUser?.Nickname ?? user?.Username}";
else else
return $"@unknown-user";
return $"";
case TagHandling.NameNoPrefix:
if (user != null)
return $"{guildUser?.Nickname ?? user?.Username}";
else
return $"";
case TagHandling.FullName: case TagHandling.FullName:
if (user != null) if (user != null)
return $"@{user.Username}#{user.Discriminator}"; return $"@{user.Username}#{user.Discriminator}";
else else
return $"@unknown-user";
return $"";
case TagHandling.FullNameNoPrefix:
if (user != null)
return $"{user.Username}#{user.Discriminator}";
else
return $"";
case TagHandling.Sanitize: case TagHandling.Sanitize:
if (guildUser != null && guildUser.Nickname == null) if (guildUser != null && guildUser.Nickname == null)
return MentionUser($"{SanitizeChar}{tag.Key}", false); return MentionUser($"{SanitizeChar}{tag.Key}", false);
@@ -166,7 +179,13 @@ namespace Discord
if (channel != null) if (channel != null)
return $"#{channel.Name}"; return $"#{channel.Name}";
else else
return $"#deleted-channel";
return $"";
case TagHandling.NameNoPrefix:
case TagHandling.FullNameNoPrefix:
if (channel != null)
return $"{channel.Name}";
else
return $"";
case TagHandling.Sanitize: case TagHandling.Sanitize:
return MentionChannel($"{SanitizeChar}{tag.Key}"); return MentionChannel($"{SanitizeChar}{tag.Key}");
} }
@@ -185,7 +204,13 @@ namespace Discord
if (role != null) if (role != null)
return $"@{role.Name}"; return $"@{role.Name}";
else else
return $"@deleted-role";
return $"";
case TagHandling.NameNoPrefix:
case TagHandling.FullNameNoPrefix:
if (role != null)
return $"{role.Name}";
else
return $"";
case TagHandling.Sanitize: case TagHandling.Sanitize:
return MentionRole($"{SanitizeChar}{tag.Key}"); return MentionRole($"{SanitizeChar}{tag.Key}");
} }
@@ -200,7 +225,9 @@ namespace Discord
{ {
case TagHandling.Name: case TagHandling.Name:
case TagHandling.FullName: case TagHandling.FullName:
return "@everyone";
case TagHandling.NameNoPrefix:
case TagHandling.FullNameNoPrefix:
return "everyone";
case TagHandling.Sanitize: case TagHandling.Sanitize:
return $"@{SanitizeChar}everyone"; return $"@{SanitizeChar}everyone";
} }
@@ -215,9 +242,11 @@ namespace Discord
{ {
case TagHandling.Name: case TagHandling.Name:
case TagHandling.FullName: case TagHandling.FullName:
return "@everyone";
case TagHandling.NameNoPrefix:
case TagHandling.FullNameNoPrefix:
return "here";
case TagHandling.Sanitize: case TagHandling.Sanitize:
return $"@{SanitizeChar}everyone";
return $"@{SanitizeChar}here";
} }
} }
return ""; return "";
@@ -232,8 +261,11 @@ namespace Discord
case TagHandling.Name: case TagHandling.Name:
case TagHandling.FullName: case TagHandling.FullName:
return $":{emoji.Name}:"; return $":{emoji.Name}:";
case TagHandling.NameNoPrefix:
case TagHandling.FullNameNoPrefix:
return $"{emoji.Name}";
case TagHandling.Sanitize: case TagHandling.Sanitize:
return $"<@{SanitizeChar}everyone";
return $"<{emoji.Id}{SanitizeChar}:{SanitizeChar}{emoji.Name}>";
} }
} }
return ""; return "";


+ 158
- 124
src/Discord.Net.Core/Utils/Preconditions.cs View File

@@ -5,148 +5,182 @@ namespace Discord
internal static class Preconditions internal static class Preconditions
{ {
//Objects //Objects
public static void NotNull<T>(T obj, string name) where T : class { if (obj == null) throw new ArgumentNullException(name); }
public static void NotNull<T>(Optional<T> obj, string name) where T : class { if (obj.IsSpecified && obj.Value == null) throw new ArgumentNullException(name); }
public static void NotNull<T>(T obj, string name, string msg = null) where T : class { if (obj == null) throw CreateNotNullException(name, msg); }
public static void NotNull<T>(Optional<T> obj, string name, string msg = null) where T : class { if (obj.IsSpecified && obj.Value == null) throw CreateNotNullException(name, msg); }

private static ArgumentNullException CreateNotNullException(string name, string msg)
{
if (msg == null) return new ArgumentNullException(name);
else return new ArgumentNullException(name, msg);
}


//Strings //Strings
public static void NotEmpty(string obj, string name) { if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); }
public static void NotEmpty(Optional<string> obj, string name) { if (obj.IsSpecified && obj.Value.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); }
public static void NotNullOrEmpty(string obj, string name)
public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw CreateNotEmptyException(name, msg); }
public static void NotEmpty(Optional<string> obj, string name, string msg = null) { if (obj.IsSpecified && obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); }
public static void NotNullOrEmpty(string obj, string name, string msg = null)
{ {
if (obj == null)
throw new ArgumentNullException(name);
if (obj.Length == 0)
throw new ArgumentException("Argument cannot be empty.", name);
if (obj == null) throw CreateNotNullException(name, msg);
if (obj.Length == 0) throw CreateNotEmptyException(name, msg);
} }
public static void NotNullOrEmpty(Optional<string> obj, string name)
public static void NotNullOrEmpty(Optional<string> obj, string name, string msg = null)
{ {
if (obj.IsSpecified) if (obj.IsSpecified)
{ {
if (obj.Value == null)
throw new ArgumentNullException(name);
if (obj.Value.Length == 0)
throw new ArgumentException("Argument cannot be empty.", name);
if (obj.Value == null) throw CreateNotNullException(name, msg);
if (obj.Value.Length == 0) throw CreateNotEmptyException(name, msg);
} }
} }
public static void NotNullOrWhitespace(string obj, string name)
public static void NotNullOrWhitespace(string obj, string name, string msg = null)
{ {
if (obj == null)
throw new ArgumentNullException(name);
if (obj.Trim().Length == 0)
throw new ArgumentException("Argument cannot be blank.", name);
if (obj == null) throw CreateNotNullException(name, msg);
if (obj.Trim().Length == 0) throw CreateNotEmptyException(name, msg);
} }
public static void NotNullOrWhitespace(Optional<string> obj, string name)
public static void NotNullOrWhitespace(Optional<string> obj, string name, string msg = null)
{ {
if (obj.IsSpecified) if (obj.IsSpecified)
{ {
if (obj.Value == null)
throw new ArgumentNullException(name);
if (obj.Value.Trim().Length == 0)
throw new ArgumentException("Argument cannot be blank.", name);
if (obj.Value == null) throw CreateNotNullException(name, msg);
if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg);
} }
} }


private static ArgumentException CreateNotEmptyException(string name, string msg)
{
if (msg == null) return new ArgumentException(name, "Argument cannot be blank.");
else return new ArgumentException(name, msg);
}

//Numerics //Numerics
public static void NotEqual(sbyte obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(byte obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(short obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(ushort obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(int obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(uint obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(long obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(ulong obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(sbyte? obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(byte? obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(short? obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(ushort? obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(int? obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(uint? obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(long? obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(ulong? obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<sbyte?> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<byte?> obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<short?> obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<ushort?> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<int?> obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<uint?> obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<long?> obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(Optional<ulong?> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(sbyte obj, sbyte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(byte obj, byte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(short obj, short value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(ushort obj, ushort value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(int obj, int value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(uint obj, uint value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(long obj, long value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(ulong obj, ulong value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); }
public static void AtLeast(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(sbyte obj, sbyte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(byte obj, byte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(short obj, short value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(ushort obj, ushort value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(int obj, int value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(uint obj, uint value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(long obj, long value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(ulong obj, ulong value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); }
public static void GreaterThan(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(sbyte obj, sbyte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(byte obj, byte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(short obj, short value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(ushort obj, ushort value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(int obj, int value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(uint obj, uint value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(long obj, long value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(ulong obj, ulong value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); }
public static void AtMost(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(sbyte obj, sbyte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(byte obj, byte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(short obj, short value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(ushort obj, ushort value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(int obj, int value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(uint obj, uint value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(long obj, long value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(ulong obj, ulong value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(Optional<sbyte> obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(Optional<byte> obj, byte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(Optional<short> obj, short value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(Optional<ushort> obj, ushort value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(Optional<int> obj, int value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(Optional<uint> obj, uint value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(Optional<long> obj, long value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); }
public static void LessThan(Optional<ulong> obj, ulong value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); }
public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(byte obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(short obj, short value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(ushort obj, ushort value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(int obj, int value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(uint obj, uint value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(long obj, long value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(ulong obj, ulong value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(sbyte? obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(byte? obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(short? obj, short value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(ushort? obj, ushort value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(int? obj, int value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(uint? obj, uint value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(long? obj, long value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(ulong? obj, ulong value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<sbyte?> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<byte?> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<short?> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<ushort?> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<int?> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<uint?> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<long?> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }
public static void NotEqual(Optional<ulong?> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); }

private static ArgumentException CreateNotEqualException<T>(string name, string msg, T value)
{
if (msg == null) return new ArgumentException($"Value may not be equal to {value}", name);
else return new ArgumentException(msg, name);
}

public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(byte obj, byte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(short obj, short value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(ushort obj, ushort value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(int obj, int value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(uint obj, uint value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(long obj, long value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(ulong obj, ulong value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); }
public static void AtLeast(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); }

private static ArgumentException CreateAtLeastException<T>(string name, string msg, T value)
{
if (msg == null) return new ArgumentException($"Value must be at least {value}", name);
else return new ArgumentException(msg, name);
}

public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(byte obj, byte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(short obj, short value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(ushort obj, ushort value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(int obj, int value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(uint obj, uint value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(long obj, long value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(ulong obj, ulong value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); }
public static void GreaterThan(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); }

private static ArgumentException CreateGreaterThanException<T>(string name, string msg, T value)
{
if (msg == null) return new ArgumentException($"Value must be greater than {value}", name);
else return new ArgumentException(msg, name);
}

public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(byte obj, byte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(short obj, short value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(ushort obj, ushort value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(int obj, int value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(uint obj, uint value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(long obj, long value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(ulong obj, ulong value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); }
public static void AtMost(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); }

private static ArgumentException CreateAtMostException<T>(string name, string msg, T value)
{
if (msg == null) return new ArgumentException($"Value must be at most {value}", name);
else return new ArgumentException(msg, name);
}

public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(byte obj, byte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(short obj, short value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(ushort obj, ushort value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(int obj, int value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(uint obj, uint value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(long obj, long value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(ulong obj, ulong value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(Optional<sbyte> obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(Optional<byte> obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(Optional<short> obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(Optional<ushort> obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(Optional<int> obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(Optional<uint> obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(Optional<long> obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); }
public static void LessThan(Optional<ulong> obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); }

private static ArgumentException CreateLessThanException<T>(string name, string msg, T value)
{
if (msg == null) return new ArgumentException($"Value must be less than {value}", name);
else return new ArgumentException(msg, name);
}
} }
} }

+ 8
- 8
src/Discord.Net.Core/project.json View File

@@ -1,5 +1,5 @@
{
"version": "1.0.0-beta2-*",
{
"version": "1.0.0-*",
"description": "A .Net API wrapper and bot framework for Discord.", "description": "A .Net API wrapper and bot framework for Discord.",
"authors": [ "RogueException" ], "authors": [ "RogueException" ],


@@ -26,14 +26,14 @@
}, },


"dependencies": { "dependencies": {
"Microsoft.Win32.Primitives": "4.0.1",
"Microsoft.Win32.Primitives": "4.3.0",
"Newtonsoft.Json": "9.0.1", "Newtonsoft.Json": "9.0.1",
"System.Collections.Concurrent": "4.0.12",
"System.Collections.Immutable": "1.2.0",
"System.Interactive.Async": "3.0.0",
"System.Net.Http": "4.1.0",
"System.Collections.Concurrent": "4.3.0",
"System.Collections.Immutable": "1.3.0",
"System.Interactive.Async": "3.1.0",
"System.Net.Http": "4.3.0",
"System.Net.WebSockets.Client": { "System.Net.WebSockets.Client": {
"version": "4.0.0",
"version": "4.3.0",
"type": "build" "type": "build"
} }
}, },


+ 44
- 0
src/Discord.Net.Rest/Discord.Net.Rest.csproj View File

@@ -0,0 +1,44 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Description>A core Discord.Net library containing the REST client and models.</Description>
<VersionPrefix>1.0.0-beta2</VersionPrefix>
<TargetFramework>netstandard1.3</TargetFramework>
<AssemblyName>Discord.Net.Rest</AssemblyName>
<PackageTags>discord;discordapp</PackageTags>
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
<EmbeddedResource Include="compiler\resources\**\*" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161104-2</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="System.IO.FileSystem">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Label="Configuration">
<SignAssembly>False</SignAssembly>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
<WarningsAsErrors>true</WarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

+ 0
- 19
src/Discord.Net.Rest/Discord.Net.Rest.xproj View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>bfc6dc28-0351-4573-926a-d4124244c04f</ProjectGuid>
<RootNamespace>Discord.Rest</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

+ 11
- 2
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -8,6 +8,8 @@ namespace Discord.Rest
{ {
public class DiscordRestClient : BaseDiscordClient, IDiscordClient public class DiscordRestClient : BaseDiscordClient, IDiscordClient
{ {
private RestApplication _applicationInfo;

public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser;


public DiscordRestClient() : this(new DiscordRestConfig()) { } public DiscordRestClient() : this(new DiscordRestConfig()) { }
@@ -21,10 +23,17 @@ namespace Discord.Rest
base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser);
return Task.CompletedTask; return Task.CompletedTask;
} }
protected override Task OnLogoutAsync()
{
_applicationInfo = null;
return Task.CompletedTask;
}


/// <inheritdoc /> /// <inheritdoc />
public Task<RestApplication> GetApplicationInfoAsync()
=> ClientHelper.GetApplicationInfoAsync(this);
public async Task<RestApplication> GetApplicationInfoAsync()
{
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this));
}
/// <inheritdoc /> /// <inheritdoc />
public Task<RestChannel> GetChannelAsync(ulong id) public Task<RestChannel> GetChannelAsync(ulong id)


+ 0
- 1
src/Discord.Net.Rest/DiscordRestConfig.cs View File

@@ -6,7 +6,6 @@ namespace Discord.Rest
{ {
public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})";
internal const int RestTimeout = 10000;
internal const int MessageQueueInterval = 100; internal const int MessageQueueInterval = 100;
internal const int WebSocketQueueInterval = 100; internal const int WebSocketQueueInterval = 100;




+ 55
- 22
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -6,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Channel; using Model = Discord.API.Channel;
using UserModel = Discord.API.User;


namespace Discord.Rest namespace Discord.Rest
{ {
@@ -43,13 +44,13 @@ namespace Discord.Rest
} }


//Invites //Invites
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IChannel channel, BaseDiscordClient client,
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client,
RequestOptions options) RequestOptions options)
{ {
var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options).ConfigureAwait(false); var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options).ConfigureAwait(false);
return models.Select(x => RestInviteMetadata.Create(client, null, channel, x)).ToImmutableArray(); return models.Select(x => RestInviteMetadata.Create(client, null, channel, x)).ToImmutableArray();
} }
public static async Task<RestInviteMetadata> CreateInviteAsync(IChannel channel, BaseDiscordClient client,
public static async Task<RestInviteMetadata> CreateInviteAsync(IGuildChannel channel, BaseDiscordClient client,
int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) int? maxAge, int? maxUses, bool isTemporary, RequestOptions options)
{ {
var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; var args = new CreateChannelInviteParams { IsTemporary = isTemporary };
@@ -62,18 +63,24 @@ namespace Discord.Rest
} }


//Messages //Messages
public static async Task<RestMessage> GetMessageAsync(IChannel channel, BaseDiscordClient client,
ulong id, IGuild guild, RequestOptions options)
public static async Task<RestMessage> GetMessageAsync(IMessageChannel channel, BaseDiscordClient client,
ulong id, RequestOptions options)
{ {
var guildId = (channel as IGuildChannel)?.GuildId;
var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null;
var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false);
return RestMessage.Create(client, guild, model);
var author = GetAuthor(client, guild, model.Author.Value);
return RestMessage.Create(client, channel, author, model);
} }
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IChannel channel, BaseDiscordClient client,
ulong? fromMessageId, Direction dir, int limit, IGuild guild, RequestOptions options)
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client,
ulong? fromMessageId, Direction dir, int limit, RequestOptions options)
{ {
if (dir == Direction.Around) if (dir == Direction.Around)
throw new NotImplementedException(); //TODO: Impl throw new NotImplementedException(); //TODO: Impl


var guildId = (channel as IGuildChannel)?.GuildId;
var guild = guildId != null ? (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null;

return new PagedAsyncEnumerable<RestMessage>( return new PagedAsyncEnumerable<RestMessage>(
DiscordConfig.MaxMessagesPerBatch, DiscordConfig.MaxMessagesPerBatch,
async (info, ct) => async (info, ct) =>
@@ -85,8 +92,15 @@ namespace Discord.Rest
}; };
if (info.Position != null) if (info.Position != null)
args.RelativeMessageId = info.Position.Value; args.RelativeMessageId = info.Position.Value;

var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options).ConfigureAwait(false); var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options).ConfigureAwait(false);
return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray();
var builder = ImmutableArray.CreateBuilder<RestMessage>();
foreach (var model in models)
{
var author = GetAuthor(client, guild, model.Author.Value);
builder.Add(RestMessage.Create(client, channel, author, model));
}
return builder.ToImmutable();
}, },
nextPage: (info, lastPage) => nextPage: (info, lastPage) =>
{ {
@@ -102,37 +116,45 @@ namespace Discord.Rest
count: limit count: limit
); );
} }
public static async Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client,
IGuild guild, RequestOptions options)
public static async Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(IMessageChannel channel, BaseDiscordClient client,
RequestOptions options)
{ {
var guildId = (channel as IGuildChannel)?.GuildId;
var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null;
var models = await client.ApiClient.GetPinsAsync(channel.Id, options).ConfigureAwait(false); var models = await client.ApiClient.GetPinsAsync(channel.Id, options).ConfigureAwait(false);
return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray();
var builder = ImmutableArray.CreateBuilder<RestMessage>();
foreach (var model in models)
{
var author = GetAuthor(client, guild, model.Author.Value);
builder.Add(RestMessage.Create(client, channel, author, model));
}
return builder.ToImmutable();
} }


public static async Task<RestUserMessage> SendMessageAsync(IChannel channel, BaseDiscordClient client,
string text, bool isTTS, IGuild guild, RequestOptions options)
public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client,
string text, bool isTTS, EmbedBuilder embed, RequestOptions options)
{ {
var args = new CreateMessageParams(text) { IsTTS = isTTS };
var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.Build() };
var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false);
return RestUserMessage.Create(client, guild, model);
return RestUserMessage.Create(client, channel, client.CurrentUser, model);
} }


public static async Task<RestUserMessage> SendFileAsync(IChannel channel, BaseDiscordClient client,
string filePath, string text, bool isTTS, IGuild guild, RequestOptions options)
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
string filePath, string text, bool isTTS, RequestOptions options)
{ {
string filename = Path.GetFileName(filePath); string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath)) using (var file = File.OpenRead(filePath))
return await SendFileAsync(channel, client, file, filename, text, isTTS, guild, options).ConfigureAwait(false);
return await SendFileAsync(channel, client, file, filename, text, isTTS, options).ConfigureAwait(false);
} }
public static async Task<RestUserMessage> SendFileAsync(IChannel channel, BaseDiscordClient client,
Stream stream, string filename, string text, bool isTTS, IGuild guild, RequestOptions options)
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
Stream stream, string filename, string text, bool isTTS, RequestOptions options)
{ {
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS };
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false);
return RestUserMessage.Create(client, guild, model);
return RestUserMessage.Create(client, channel, client.CurrentUser, model);
} }


public static async Task DeleteMessagesAsync(IChannel channel, BaseDiscordClient client,
public static async Task DeleteMessagesAsync(IMessageChannel channel, BaseDiscordClient client,
IEnumerable<IMessage> messages, RequestOptions options) IEnumerable<IMessage> messages, RequestOptions options)
{ {
var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray());
@@ -216,5 +238,16 @@ namespace Discord.Rest
public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client,
RequestOptions options) RequestOptions options)
=> new TypingNotifier(client, channel, options); => new TypingNotifier(client, channel, options);

//Helpers
private static IUser GetAuthor(BaseDiscordClient client, IGuild guild, UserModel model)
{
IUser author = null;
if (guild != null)
author = guild.GetUserAsync(model.Id, CacheMode.CacheOnly).Result;
if (author == null)
author = RestUser.Create(client, model);
return author;
}
} }
} }

+ 1
- 1
src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs View File

@@ -7,7 +7,7 @@ namespace Discord.Rest
public interface IRestMessageChannel : IMessageChannel public interface IRestMessageChannel : IMessageChannel
{ {
/// <summary> Sends a message to this message channel. </summary> /// <summary> Sends a message to this message channel. </summary>
new Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null);
new Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null);
/// <summary> Sends a file to this text channel, with an optional caption. </summary> /// <summary> Sends a file to this text channel, with an optional caption. </summary>
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null);
/// <summary> Sends a file to this text channel, with an optional caption. </summary> /// <summary> Sends a file to this text channel, with an optional caption. </summary>


+ 11
- 11
src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs View File

@@ -53,22 +53,22 @@ namespace Discord.Rest
} }


public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
@@ -126,8 +126,8 @@ namespace Discord.Rest
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options)
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options); => EnterTypingState(options);




+ 11
- 11
src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs View File

@@ -66,22 +66,22 @@ namespace Discord.Rest
} }


public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
@@ -136,8 +136,8 @@ namespace Discord.Rest
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options)
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options); => EnterTypingState(options);




+ 11
- 11
src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs View File

@@ -45,22 +45,22 @@ namespace Discord.Rest
=> ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options);


public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
@@ -108,8 +108,8 @@ namespace Discord.Rest
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options)
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options); => EnterTypingState(options);




+ 11
- 11
src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs View File

@@ -23,22 +23,22 @@ namespace Discord.Rest
} }
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS, EmbedBuilder embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
@@ -86,8 +86,8 @@ namespace Discord.Rest
=> await SendFileAsync(filePath, text, isTTS, options); => await SendFileAsync(filePath, text, isTTS, options);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, options); => await SendFileAsync(stream, filename, text, isTTS, options);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options)
=> await SendMessageAsync(text, isTTS, options);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options);
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options); => EnterTypingState(options);




+ 1
- 1
src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs View File

@@ -142,7 +142,7 @@ namespace Discord.Rest
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));


var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false); var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false);
var role = RestRole.Create(client, model);
var role = RestRole.Create(client, guild, model);


await role.ModifyAsync(x => await role.ModifyAsync(x =>
{ {


+ 1
- 1
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs View File

@@ -87,7 +87,7 @@ namespace Discord.Rest
if (model.Roles != null) if (model.Roles != null)
{ {
for (int i = 0; i < model.Roles.Length; i++) for (int i = 0; i < model.Roles.Length; i++)
roles[model.Roles[i].Id] = RestRole.Create(Discord, model.Roles[i]);
roles[model.Roles[i].Id] = RestRole.Create(Discord, this, model.Roles[i]);
} }
_roles = roles.ToImmutable(); _roles = roles.ToImmutable();




+ 39
- 4
src/Discord.Net.Rest/Entities/Messages/Embed.cs View File

@@ -1,4 +1,7 @@
using System.Diagnostics;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Model = Discord.API.Embed; using Model = Discord.API.Embed;


namespace Discord namespace Discord
@@ -10,23 +13,55 @@ namespace Discord
public string Url { get; } public string Url { get; }
public string Title { get; } public string Title { get; }
public string Type { get; } public string Type { get; }
public DateTimeOffset? Timestamp { get; }
public Color? Color { get; }
public EmbedImage? Image { get; }
public EmbedVideo? Video { get; }
public EmbedAuthor? Author { get; }
public EmbedFooter? Footer { get; }
public EmbedProvider? Provider { get; } public EmbedProvider? Provider { get; }
public EmbedThumbnail? Thumbnail { get; } public EmbedThumbnail? Thumbnail { get; }
public ImmutableArray<EmbedField> Fields { get; }


internal Embed(string type, string title, string description, string url, EmbedProvider? provider, EmbedThumbnail? thumbnail)
internal Embed(string type,
string title,
string description,
string url,
DateTimeOffset? timestamp,
Color? color,
EmbedImage? image,
EmbedVideo? video,
EmbedAuthor? author,
EmbedFooter? footer,
EmbedProvider? provider,
EmbedThumbnail? thumbnail,
ImmutableArray<EmbedField> fields)
{ {
Type = type; Type = type;
Title = title; Title = title;
Description = description; Description = description;
Url = url; Url = url;
Color = color;
Timestamp = timestamp;
Image = image;
Video = video;
Author = author;
Footer = footer;
Provider = provider; Provider = provider;
Thumbnail = thumbnail; Thumbnail = thumbnail;
Fields = fields;
} }
internal static Embed Create(Model model) internal static Embed Create(Model model)
{ {
return new Embed(model.Type, model.Title, model.Description, model.Url,
return new Embed(model.Type, model.Title, model.Description, model.Url,model.Timestamp,
model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null,
model.Image.IsSpecified ? EmbedImage.Create(model.Image.Value) : (EmbedImage?)null,
model.Video.IsSpecified ? EmbedVideo.Create(model.Video.Value) : (EmbedVideo?)null,
model.Author.IsSpecified ? EmbedAuthor.Create(model.Author.Value) : (EmbedAuthor?)null,
model.Footer.IsSpecified ? EmbedFooter.Create(model.Footer.Value) : (EmbedFooter?)null,
model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null,
model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null);
model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null,
model.Fields.IsSpecified ? model.Fields.Value.Select(EmbedField.Create).ToImmutableArray() : ImmutableArray.Create<EmbedField>());
} }


public override string ToString() => Title; public override string ToString() => Title;


+ 6
- 1
src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs View File

@@ -15,7 +15,12 @@ namespace Discord.Rest
{ {
var args = new ModifyMessageParams(); var args = new ModifyMessageParams();
func(args); func(args);
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, args, options).ConfigureAwait(false);
var apiArgs = new API.Rest.ModifyMessageParams
{
Content = args.Content,
Embed = args.Embed.IsSpecified ? args.Embed.Value.Build() : Optional.Create<API.Embed>()
};
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false);
} }
public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client,
RequestOptions options) RequestOptions options)


+ 5
- 7
src/Discord.Net.Rest/Entities/Messages/RestMessage.cs View File

@@ -9,11 +9,10 @@ namespace Discord.Rest
{ {
public abstract class RestMessage : RestEntity<ulong>, IMessage, IUpdateable public abstract class RestMessage : RestEntity<ulong>, IMessage, IUpdateable
{ {
internal readonly IGuild _guild;
private long _timestampTicks; private long _timestampTicks;


public IMessageChannel Channel { get; } public IMessageChannel Channel { get; }
public RestUser Author { get; }
public IUser Author { get; }


public string Content { get; private set; } public string Content { get; private set; }


@@ -32,19 +31,18 @@ namespace Discord.Rest


public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);


internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild)
internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author)
: base(discord, id) : base(discord, id)
{ {
Channel = channel; Channel = channel;
Author = author; Author = author;
_guild = guild;
} }
internal static RestMessage Create(BaseDiscordClient discord, IGuild guild, Model model)
internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model)
{ {
if (model.Type == MessageType.Default) if (model.Type == MessageType.Default)
return RestUserMessage.Create(discord, guild, model);
return RestUserMessage.Create(discord, channel, author, model);
else else
return RestSystemMessage.Create(discord, guild, model);
return RestSystemMessage.Create(discord, channel, author, model);
} }
internal virtual void Update(Model model) internal virtual void Update(Model model)
{ {


+ 4
- 6
src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs View File

@@ -8,15 +8,13 @@ namespace Discord.Rest
{ {
public MessageType Type { get; private set; } public MessageType Type { get; private set; }


internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild)
: base(discord, id, channel, author, guild)
internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author)
: base(discord, id, channel, author)
{ {
} }
internal new static RestSystemMessage Create(BaseDiscordClient discord, IGuild guild, Model model)
internal new static RestSystemMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model)
{ {
var entity = new RestSystemMessage(discord, model.Id,
RestVirtualMessageChannel.Create(discord, model.ChannelId),
RestUser.Create(discord, model.Author.Value), guild);
var entity = new RestSystemMessage(discord, model.Id, channel, author);
entity.Update(model); entity.Update(model);
return entity; return entity;
} }


+ 11
- 9
src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs View File

@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Message; using Model = Discord.API.Message;


@@ -32,15 +31,13 @@ namespace Discord.Rest
public override IReadOnlyCollection<ITag> Tags => _tags; public override IReadOnlyCollection<ITag> Tags => _tags;
public IReadOnlyDictionary<Emoji, int> Reactions => _reactions.ToDictionary(x => x.Emoji, x => x.Count); public IReadOnlyDictionary<Emoji, int> Reactions => _reactions.ToDictionary(x => x.Emoji, x => x.Count);


internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild)
: base(discord, id, channel, author, guild)
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author)
: base(discord, id, channel, author)
{ {
} }
internal new static RestUserMessage Create(BaseDiscordClient discord, IGuild guild, Model model)
internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model)
{ {
var entity = new RestUserMessage(discord, model.Id,
RestVirtualMessageChannel.Create(discord, model.ChannelId),
RestUser.Create(discord, model.Author.Value), guild);
var entity = new RestUserMessage(discord, model.Id, channel, author);
entity.Update(model); entity.Update(model);
return entity; return entity;
} }
@@ -122,7 +119,9 @@ namespace Discord.Rest
if (model.Content.IsSpecified) if (model.Content.IsSpecified)
{ {
var text = model.Content.Value; var text = model.Content.Value;
_tags = MessageHelper.ParseTags(text, null, _guild, mentions);
var guildId = (Channel as IGuildChannel)?.GuildId;
var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null;
_tags = MessageHelper.ParseTags(text, null, guild, mentions);
model.Content = text; model.Content = text;
} }
} }
@@ -155,9 +154,12 @@ namespace Discord.Rest
public Task UnpinAsync(RequestOptions options) public Task UnpinAsync(RequestOptions options)
=> MessageHelper.UnpinAsync(this, Discord, options); => MessageHelper.UnpinAsync(this, Discord, options);


public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
=> MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
=> MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);


private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})";
} }


+ 16
- 5
src/Discord.Net.Rest/Entities/Roles/RestRole.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Rest
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestRole : RestEntity<ulong>, IRole public class RestRole : RestEntity<ulong>, IRole
{ {
public RestGuild Guild { get; }
internal IGuild Guild { get; }
public Color Color { get; private set; } public Color Color { get; private set; }
public bool IsHoisted { get; private set; } public bool IsHoisted { get; private set; }
public bool IsManaged { get; private set; } public bool IsManaged { get; private set; }
@@ -22,13 +22,14 @@ namespace Discord.Rest
public bool IsEveryone => Id == Guild.Id; public bool IsEveryone => Id == Guild.Id;
public string Mention => MentionUtils.MentionRole(Id); public string Mention => MentionUtils.MentionRole(Id);


internal RestRole(BaseDiscordClient discord, ulong id)
internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id)
: base(discord, id) : base(discord, id)
{ {
Guild = guild;
} }
internal static RestRole Create(BaseDiscordClient discord, Model model)
internal static RestRole Create(BaseDiscordClient discord, IGuild guild, Model model)
{ {
var entity = new RestRole(discord, model.Id);
var entity = new RestRole(discord, guild, model.Id);
entity.Update(model); entity.Update(model);
return entity; return entity;
} }
@@ -51,10 +52,20 @@ namespace Discord.Rest
public Task DeleteAsync(RequestOptions options = null) public Task DeleteAsync(RequestOptions options = null)
=> RoleHelper.DeleteAsync(this, Discord, options); => RoleHelper.DeleteAsync(this, Discord, options);


public int CompareTo(IRole role) => this.Compare(role);

public override string ToString() => Name; public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id})"; private string DebuggerDisplay => $"{Name} ({Id})";


//IRole //IRole
IGuild IRole.Guild => Guild;
IGuild IRole.Guild
{
get
{
if (Guild != null)
return Guild;
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
}
}
} }
} }

+ 4
- 4
src/Discord.Net.Rest/project.json View File

@@ -1,5 +1,5 @@
{
"version": "1.0.0-beta2-*",
{
"version": "1.0.0-*",
"description": "A core Discord.Net library containing the REST client and models.", "description": "A core Discord.Net library containing the REST client and models.",
"authors": [ "RogueException" ], "authors": [ "RogueException" ],


@@ -29,7 +29,7 @@
"Discord.Net.Core": { "Discord.Net.Core": {
"target": "project" "target": "project"
}, },
"System.IO.FileSystem": "4.0.1"
"System.IO.FileSystem": "4.3.0"
}, },


"frameworks": { "frameworks": {
@@ -41,4 +41,4 @@
] ]
} }
} }
}
}

+ 48
- 0
src/Discord.Net.Rpc/Discord.Net.Rpc.csproj View File

@@ -0,0 +1,48 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Description>A core Discord.Net library containing the RPC client and models.</Description>
<VersionPrefix>1.0.0-beta2</VersionPrefix>
<TargetFramework>netstandard1.3</TargetFramework>
<AssemblyName>Discord.Net.Rpc</AssemblyName>
<PackageTags>discord;discordapp</PackageTags>
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
<EmbeddedResource Include="compiler\resources\**\*" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161104-2</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="System.IO.Compression">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Net.WebSockets.Client">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Label="Configuration">
<SignAssembly>False</SignAssembly>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
<WarningsAsErrors>true</WarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

+ 0
- 19
src/Discord.Net.Rpc/Discord.Net.Rpc.xproj View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>5688a353-121e-40a1-8bfa-b17b91fb48fb</ProjectGuid>
<RootNamespace>Discord.Rpc</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

+ 6
- 3
src/Discord.Net.Rpc/DiscordRpcClient.cs View File

@@ -14,7 +14,7 @@ using System.Threading.Tasks;


namespace Discord.Rpc namespace Discord.Rpc
{ {
public partial class DiscordRpcClient : BaseDiscordClient
public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient
{ {
private readonly Logger _rpcLogger; private readonly Logger _rpcLogger;
private readonly JsonSerializer _serializer; private readonly JsonSerializer _serializer;
@@ -33,7 +33,7 @@ namespace Discord.Rpc


public new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient; public new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient;
public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } } public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } }
public RestApplication CurrentApplication { get; private set; }
public RestApplication ApplicationInfo { get; private set; }


/// <summary> Creates a new RPC discord client. </summary> /// <summary> Creates a new RPC discord client. </summary>
public DiscordRpcClient(string clientId, string origin) public DiscordRpcClient(string clientId, string origin)
@@ -393,7 +393,7 @@ namespace Discord.Rpc
{ {
var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false);
CurrentUser = RestSelfUser.Create(this, response.User); CurrentUser = RestSelfUser.Create(this, response.User);
CurrentApplication = RestApplication.Create(this, response.Application);
ApplicationInfo = RestApplication.Create(this, response.Application);
Scopes = response.Scopes; Scopes = response.Scopes;
TokenExpiresAt = response.Expires; TokenExpiresAt = response.Expires;


@@ -547,5 +547,8 @@ namespace Discord.Rpc
return; return;
} }
} }

//IDiscordClient
Task<IApplication> IDiscordClient.GetApplicationInfoAsync() => Task.FromResult<IApplication>(ApplicationInfo);
} }
} }

+ 11
- 11
src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs View File

@@ -34,22 +34,22 @@ namespace Discord.Rpc


//TODO: Use RPC cache //TODO: Use RPC cache
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
@@ -104,8 +104,8 @@ namespace Discord.Rpc
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options)
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options); => EnterTypingState(options);




+ 11
- 11
src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs View File

@@ -36,22 +36,22 @@ namespace Discord.Rpc


//TODO: Use RPC cache //TODO: Use RPC cache
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
@@ -103,8 +103,8 @@ namespace Discord.Rpc
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options)
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options); => EnterTypingState(options);




+ 11
- 11
src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs View File

@@ -39,22 +39,22 @@ namespace Discord.Rpc


//TODO: Use RPC cache //TODO: Use RPC cache
public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null) public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);


public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
@@ -105,8 +105,8 @@ namespace Discord.Rpc
=> await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options)
=> await SendMessageAsync(text, isTTS, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options); => EnterTypingState(options);
} }


+ 4
- 1
src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs View File

@@ -123,9 +123,12 @@ namespace Discord.Rpc
public Task UnpinAsync(RequestOptions options) public Task UnpinAsync(RequestOptions options)
=> MessageHelper.UnpinAsync(this, Discord, options); => MessageHelper.UnpinAsync(this, Discord, options);


public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
=> MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
=> MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);


private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})";
} }


+ 4
- 4
src/Discord.Net.Rpc/project.json View File

@@ -1,5 +1,5 @@
{ {
"version": "1.0.0-beta2-*",
"version": "1.0.0-*",
"description": "A core Discord.Net library containing the RPC client and models.", "description": "A core Discord.Net library containing the RPC client and models.",
"authors": [ "RogueException" ], "authors": [ "RogueException" ],


@@ -32,8 +32,8 @@
"Discord.Net.Rest": { "Discord.Net.Rest": {
"target": "project" "target": "project"
}, },
"System.IO.Compression": "4.1.0",
"System.Net.WebSockets.Client": "4.0.0"
"System.IO.Compression": "4.3.0",
"System.Net.WebSockets.Client": "4.3.0"
}, },


"frameworks": { "frameworks": {
@@ -45,4 +45,4 @@
] ]
} }
} }
}
}

+ 58
- 0
src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj View File

@@ -0,0 +1,58 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Description>A core Discord.Net library containing the WebSocket client and models.</Description>
<VersionPrefix>1.0.0-beta2</VersionPrefix>
<TargetFramework>netstandard1.3</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Discord.Net.WebSocket</AssemblyName>
<PackageTags>discord;discordapp</PackageTags>
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
<EmbeddedResource Include="compiler\resources\**\*" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161104-2</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="System.IO.Compression">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Net.NameResolution">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Net.Sockets">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Net.WebSockets.Client">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Runtime.InteropServices">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Label="Configuration">
<SignAssembly>False</SignAssembly>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
<WarningsAsErrors>true</WarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

+ 0
- 19
src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d</ProjectGuid>
<RootNamespace>Discord.WebSocket</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

+ 6
- 2
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -38,6 +38,7 @@ namespace Discord.WebSocket
private int _nextAudioId; private int _nextAudioId;
private bool _canReconnect; private bool _canReconnect;
private DateTimeOffset? _statusSince; private DateTimeOffset? _statusSince;
private RestApplication _applicationInfo;


/// <summary> Gets the shard of of this client. </summary> /// <summary> Gets the shard of of this client. </summary>
public int ShardId { get; } public int ShardId { get; }
@@ -123,6 +124,7 @@ namespace Discord.WebSocket
if (ConnectionState != ConnectionState.Disconnected) if (ConnectionState != ConnectionState.Disconnected)
await DisconnectInternalAsync(null, false).ConfigureAwait(false); await DisconnectInternalAsync(null, false).ConfigureAwait(false);


_applicationInfo = null;
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>();
} }
@@ -333,8 +335,10 @@ namespace Discord.WebSocket
} }


/// <inheritdoc /> /// <inheritdoc />
public Task<RestApplication> GetApplicationInfoAsync()
=> ClientHelper.GetApplicationInfoAsync(this);
public async Task<RestApplication> GetApplicationInfoAsync()
{
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this));
}


/// <inheritdoc /> /// <inheritdoc />
public SocketGuild GetGuild(ulong id) public SocketGuild GetGuild(ulong id)


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save