Browse Source

Merge branch 'dev' into feature/delete-msg-by-id

pull/996/head
Christopher F GitHub 7 years ago
parent
commit
ddcfed1ed3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 2243 additions and 200 deletions
  1. +34
    -2
      Discord.Net.sln
  2. +16
    -0
      docs/README.md
  3. +2
    -2
      docs/docfx.json
  4. +12
    -0
      samples/01_basic_ping_bot/01_basic_ping_bot.csproj
  5. +69
    -0
      samples/01_basic_ping_bot/Program.cs
  6. +17
    -0
      samples/02_commands_framework/02_commands_framework.csproj
  7. +63
    -0
      samples/02_commands_framework/Modules/PublicModule.cs
  8. +60
    -0
      samples/02_commands_framework/Program.cs
  9. +49
    -0
      samples/02_commands_framework/Services/CommandHandlingService.cs
  10. +20
    -0
      samples/02_commands_framework/Services/PictureService.cs
  11. +1
    -0
      src/Discord.Net.Commands/Attributes/CommandAttribute.cs
  12. +3
    -3
      src/Discord.Net.Commands/Builders/CommandBuilder.cs
  13. +0
    -1
      src/Discord.Net.Commands/Builders/ModuleBuilder.cs
  14. +2
    -1
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  15. +3
    -3
      src/Discord.Net.Commands/CommandParser.cs
  16. +44
    -7
      src/Discord.Net.Commands/CommandService.cs
  17. +0
    -6
      src/Discord.Net.Commands/CommandServiceConfig.cs
  18. +6
    -4
      src/Discord.Net.Commands/Info/CommandInfo.cs
  19. +0
    -5
      src/Discord.Net.Commands/Info/ModuleInfo.cs
  20. +2
    -2
      src/Discord.Net.Commands/Readers/ChannelTypeReader.cs
  21. +2
    -2
      src/Discord.Net.Commands/Readers/MessageTypeReader.cs
  22. +2
    -2
      src/Discord.Net.Commands/Readers/RoleTypeReader.cs
  23. +2
    -2
      src/Discord.Net.Commands/Readers/UserTypeReader.cs
  24. +5
    -1
      src/Discord.Net.Commands/Results/TypeReaderResult.cs
  25. +16
    -5
      src/Discord.Net.Core/CDN.cs
  26. +3
    -2
      src/Discord.Net.Core/Discord.Net.Core.csproj
  27. +6
    -4
      src/Discord.Net.Core/DiscordConfig.cs
  28. +8
    -5
      src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs
  29. +50
    -0
      src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs
  30. +14
    -0
      src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs
  31. +34
    -0
      src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs
  32. +1
    -1
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  33. +26
    -4
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  34. +6
    -6
      src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs
  35. +5
    -4
      src/Discord.Net.Core/Entities/Invites/IInvite.cs
  36. +13
    -2
      src/Discord.Net.Core/Entities/Messages/Embed.cs
  37. +68
    -71
      src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
  38. +2
    -2
      src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
  39. +6
    -3
      src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
  40. +11
    -1
      src/Discord.Net.Core/Entities/Roles/Color.cs
  41. +2
    -0
      src/Discord.Net.Core/Entities/Users/IUser.cs
  42. +14
    -0
      src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs
  43. +1
    -1
      src/Discord.Net.Core/Extensions/UserExtensions.cs
  44. +1
    -1
      src/Discord.Net.Core/IDiscordClient.cs
  45. +3
    -2
      src/Discord.Net.DebugTools/Discord.Net.DebugTools.csproj
  46. +16
    -0
      src/Discord.Net.Rest/API/Common/AuditLog.cs
  47. +17
    -0
      src/Discord.Net.Rest/API/Common/AuditLogChange.cs
  48. +26
    -0
      src/Discord.Net.Rest/API/Common/AuditLogEntry.cs
  49. +27
    -0
      src/Discord.Net.Rest/API/Common/AuditLogOptions.cs
  50. +5
    -1
      src/Discord.Net.Rest/API/Common/Invite.cs
  51. +5
    -5
      src/Discord.Net.Rest/API/Common/VoiceRegion.cs
  52. +12
    -1
      src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs
  53. +8
    -0
      src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs
  54. +7
    -0
      src/Discord.Net.Rest/API/Rest/GetInviteParams.cs
  55. +1
    -1
      src/Discord.Net.Rest/API/Rest/GetReactionUsersParams.cs
  56. +12
    -0
      src/Discord.Net.Rest/AssemblyInfo.cs
  57. +1
    -1
      src/Discord.Net.Rest/BaseDiscordClient.cs
  58. +8
    -4
      src/Discord.Net.Rest/ClientHelper.cs
  59. +3
    -2
      src/Discord.Net.Rest/Discord.Net.Rest.csproj
  60. +36
    -12
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  61. +4
    -4
      src/Discord.Net.Rest/DiscordRestClient.cs
  62. +58
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs
  63. +23
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs
  64. +52
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs
  65. +45
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs
  66. +18
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs
  67. +45
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs
  68. +31
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs
  69. +28
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs
  70. +31
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs
  71. +32
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs
  72. +79
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs
  73. +55
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs
  74. +55
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs
  75. +20
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs
  76. +46
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs
  77. +23
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs
  78. +50
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs
  79. +35
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs
  80. +22
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
  81. +37
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs
  82. +42
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs
  83. +44
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs
  84. +22
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs
  85. +47
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs
  86. +47
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs
  87. +21
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs
  88. +62
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs
  89. +23
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs
  90. +44
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs
  91. +46
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs
  92. +16
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs
  93. +52
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs
  94. +38
    -0
      src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs
  95. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs
  96. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  97. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
  98. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  99. +3
    -3
      src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs
  100. +55
    -5
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs

+ 34
- 2
Discord.Net.sln View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.27130.0
VisualStudioVersion = 15.0.27004.2009
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}"
EndProject EndProject
@@ -22,7 +22,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\D
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Analyzers", "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj", "{BBA8E7FB-C834-40DC-822F-B112CB7F0140}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Analyzers", "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj", "{BBA8E7FB-C834-40DC-822F-B112CB7F0140}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01_basic_ping_bot", "samples\01_basic_ping_bot\01_basic_ping_bot.csproj", "{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02_commands_framework", "samples\02_commands_framework\02_commands_framework.csproj", "{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -130,6 +136,30 @@ Global
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x64.Build.0 = Release|Any CPU {BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x64.Build.0 = Release|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x86.ActiveCfg = Release|Any CPU {BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x86.ActiveCfg = Release|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x86.Build.0 = Release|Any CPU {BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x86.Build.0 = Release|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|x64.ActiveCfg = Debug|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|x64.Build.0 = Debug|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|x86.ActiveCfg = Debug|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|x86.Build.0 = Debug|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|Any CPU.Build.0 = Release|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|x64.ActiveCfg = Release|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|x64.Build.0 = Release|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|x86.ActiveCfg = Release|Any CPU
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|x86.Build.0 = Release|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|x64.Build.0 = Debug|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|x86.ActiveCfg = Debug|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|x86.Build.0 = Debug|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|Any CPU.Build.0 = Release|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|x64.ActiveCfg = Release|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|x64.Build.0 = Release|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|x86.ActiveCfg = Release|Any CPU
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -141,6 +171,8 @@ Global
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012}
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
{BBA8E7FB-C834-40DC-822F-B112CB7F0140} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} {BBA8E7FB-C834-40DC-822F-B112CB7F0140} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495}


+ 16
- 0
docs/README.md View File

@@ -0,0 +1,16 @@
# Instructions for Building Documentation

The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing]

1. Navigate to the root of the repository.
2. (Optional) If you intend to target a specific version, ensure that you
have the correct version checked out.
3. Build the library. Run `dotnet build` in the root of this repository.
Ensure that the build passes without errors.
4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter
to preview the site locally. Some elements of the page may appear incorrect
when not hosted by a server.
- Remarks: According to the docfx website, this tool does work on Linux under mono.

[docfx-main]: https://dotnet.github.io/docfx/
[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html

+ 2
- 2
docs/docfx.json View File

@@ -67,8 +67,8 @@
"default" "default"
], ],
"globalMetadata": { "globalMetadata": {
"_appFooter": "Discord.Net (c) 2015-2017"
"_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta"
}, },
"noLangKeyword": false "noLangKeyword": false
} }
}
}

+ 12
- 0
samples/01_basic_ping_bot/01_basic_ping_bot.csproj View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" />
</ItemGroup>

</Project>

+ 69
- 0
samples/01_basic_ping_bot/Program.cs View File

@@ -0,0 +1,69 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;

namespace _01_basic_ping_bot
{
// This is a minimal, barebones example of using Discord.Net
//
// If writing a bot with commands, we recommend using the Discord.Net.Commands
// framework, rather than handling commands yourself, like we do in this sample.
//
// You can find samples of using the command framework:
// - Here, under the 02_commands_framework sample
// - https://github.com/foxbot/DiscordBotBase - a barebones bot template
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library
class Program
{
private DiscordSocketClient _client;

// Discord.Net heavily utilizes TAP for async, so we create
// an asynchronous context from the beginning.
static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();

public async Task MainAsync()
{
_client = new DiscordSocketClient();

_client.Log += LogAsync;
_client.Ready += ReadyAsync;
_client.MessageReceived += MessageReceivedAsync;

// Tokens should be considered secret data, and never hard-coded.
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
await _client.StartAsync();

// Block the program until it is closed.
await Task.Delay(-1);
}

private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}

// The Ready event indicates that the client has opened a
// connection and it is now safe to access the cache.
private Task ReadyAsync()
{
Console.WriteLine($"{_client.CurrentUser} is connected!");

return Task.CompletedTask;
}

// This is not the recommmended way to write a bot - consider
// reading over the Commands Framework sample.
private async Task MessageReceivedAsync(SocketMessage message)
{
// The bot should never respond to itself.
if (message.Author.Id == _client.CurrentUser.Id)
return;

if (message.Content == "!ping")
await message.Channel.SendMessageAsync("pong!");
}
}
}

+ 17
- 0
samples/02_commands_framework/02_commands_framework.csproj View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.0-preview1-final" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Discord.Net.Commands\Discord.Net.Commands.csproj" />
<ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" />
</ItemGroup>

</Project>

+ 63
- 0
samples/02_commands_framework/Modules/PublicModule.cs View File

@@ -0,0 +1,63 @@
using System.IO;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using _02_commands_framework.Services;

namespace _02_commands_framework.Modules
{
// Modules must be public and inherit from an IModuleBase
public class PublicModule : ModuleBase<SocketCommandContext>
{
// Dependency Injection will fill this value in for us
public PictureService PictureService { get; set; }

[Command("ping")]
[Alias("pong", "hello")]
public Task PingAsync()
=> ReplyAsync("pong!");

[Command("cat")]
public async Task CatAsync()
{
// Get a stream containing an image of a cat
var stream = await PictureService.GetCatPictureAsync();
// Streams must be seeked to their beginning before being uploaded!
stream.Seek(0, SeekOrigin.Begin);
await Context.Channel.SendFileAsync(stream, "cat.png");
}

// Get info on a user, or the user who invoked the command if one is not specified
[Command("userinfo")]
public async Task UserInfoAsync(IUser user = null)
{
user = user ?? Context.User;

await ReplyAsync(user.ToString());
}

// Ban a user
[Command("ban")]
[RequireContext(ContextType.Guild)]
// make sure the user invoking the command can ban
[RequireUserPermission(GuildPermission.BanMembers)]
// make sure the bot itself can ban
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task BanUserAsync(IGuildUser user, [Remainder] string reason = null)
{
await user.Guild.AddBanAsync(user, reason: reason);
await ReplyAsync("ok!");
}

// [Remainder] takes the rest of the command's arguments as one argument, rather than splitting every space
[Command("echo")]
public Task EchoAsync([Remainder] string text)
// Insert a ZWSP before the text to prevent triggering other bots!
=> ReplyAsync('\u200B' + text);

// 'params' will parse space-separated elements into a list
[Command("list")]
public Task ListAsync(params string[] objects)
=> ReplyAsync("You listed: " + string.Join("; ", objects));
}
}

+ 60
- 0
samples/02_commands_framework/Program.cs View File

@@ -0,0 +1,60 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Discord;
using Discord.WebSocket;
using Discord.Commands;
using _02_commands_framework.Services;

namespace _02_commands_framework
{
// This is a minimal example of using Discord.Net's command
// framework - by no means does it show everything the framework
// is capable of.
//
// You can find samples of using the command framework:
// - Here, under the 02_commands_framework sample
// - https://github.com/foxbot/DiscordBotBase - a barebones bot template
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library
class Program
{
static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();

public async Task MainAsync()
{
var services = ConfigureServices();

var client = services.GetRequiredService<DiscordSocketClient>();

client.Log += LogAsync;
services.GetRequiredService<CommandService>().Log += LogAsync;

await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
await client.StartAsync();

await services.GetRequiredService<CommandHandlingService>().InitializeAsync();

await Task.Delay(-1);
}

private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());

return Task.CompletedTask;
}

private IServiceProvider ConfigureServices()
{
return new ServiceCollection()
.AddSingleton<DiscordSocketClient>()
.AddSingleton<CommandService>()
.AddSingleton<CommandHandlingService>()
.AddSingleton<HttpClient>()
.AddSingleton<PictureService>()
.BuildServiceProvider();
}
}
}

+ 49
- 0
samples/02_commands_framework/Services/CommandHandlingService.cs View File

@@ -0,0 +1,49 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Discord;
using Discord.Commands;
using Discord.WebSocket;

namespace _02_commands_framework.Services
{
public class CommandHandlingService
{
private readonly CommandService _commands;
private readonly DiscordSocketClient _discord;
private readonly IServiceProvider _services;

public CommandHandlingService(IServiceProvider services)
{
_commands = services.GetRequiredService<CommandService>();
_discord = services.GetRequiredService<DiscordSocketClient>();
_services = services;

_discord.MessageReceived += MessageReceivedAsync;
}

public async Task InitializeAsync()
{
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
}

public async Task MessageReceivedAsync(SocketMessage rawMessage)
{
// Ignore system messages, or messages from other bots
if (!(rawMessage is SocketUserMessage message)) return;
if (message.Source != MessageSource.User) return;

// This value holds the offset where the prefix ends
var argPos = 0;
if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) return;

var context = new SocketCommandContext(_discord, message);
var result = await _commands.ExecuteAsync(context, argPos, _services);

if (result.Error.HasValue &&
result.Error.Value != CommandError.UnknownCommand) // it's bad practice to send 'unknown command' errors
await context.Channel.SendMessageAsync(result.ToString());
}
}
}

+ 20
- 0
samples/02_commands_framework/Services/PictureService.cs View File

@@ -0,0 +1,20 @@
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

namespace _02_commands_framework.Services
{
public class PictureService
{
private readonly HttpClient _http;

public PictureService(HttpClient http)
=> _http = http;

public async Task<Stream> GetCatPictureAsync()
{
var resp = await _http.GetAsync("https://cataas.com/cat");
return await resp.Content.ReadAsStreamAsync();
}
}
}

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

@@ -7,6 +7,7 @@ namespace Discord.Commands
{ {
public string Text { get; } public string Text { get; }
public RunMode RunMode { get; set; } = RunMode.Default; public RunMode RunMode { get; set; } = RunMode.Default;
public bool? IgnoreExtraArgs { get; set; }


public CommandAttribute() public CommandAttribute()
{ {


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

@@ -1,8 +1,7 @@
using System;
using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;


namespace Discord.Commands.Builders namespace Discord.Commands.Builders
{ {
@@ -22,6 +21,7 @@ namespace Discord.Commands.Builders
public string PrimaryAlias { get; set; } public string PrimaryAlias { get; set; }
public RunMode RunMode { get; set; } public RunMode RunMode { get; set; }
public int Priority { get; set; } public int Priority { get; set; }
public bool IgnoreExtraArgs { get; set; }


public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions; public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions;
public IReadOnlyList<ParameterBuilder> Parameters => _parameters; public IReadOnlyList<ParameterBuilder> Parameters => _parameters;
@@ -140,4 +140,4 @@ namespace Discord.Commands.Builders
return new CommandInfo(this, info, service); return new CommandInfo(this, info, service);
} }
} }
}
}

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

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;


namespace Discord.Commands.Builders namespace Discord.Commands.Builders
{ {


+ 2
- 1
src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs View File

@@ -158,6 +158,7 @@ namespace Discord.Commands
builder.AddAliases(command.Text); builder.AddAliases(command.Text);
builder.RunMode = command.RunMode; builder.RunMode = command.RunMode;
builder.Name = builder.Name ?? command.Text; builder.Name = builder.Name ?? command.Text;
builder.IgnoreExtraArgs = command.IgnoreExtraArgs ?? service._ignoreExtraArgs;
break; break;
case NameAttribute name: case NameAttribute name:
builder.Name = name.Text; builder.Name = name.Text;
@@ -291,7 +292,7 @@ namespace Discord.Commands


//We dont have a cached type reader, create one //We dont have a cached type reader, create one
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, services); reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, services);
service.AddTypeReader(paramType, reader);
service.AddTypeReader(paramType, reader, false);


return reader; return reader;
} }


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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -14,7 +14,7 @@ namespace Discord.Commands
QuotedParameter QuotedParameter
} }
public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos)
public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos)
{ {
ParameterInfo curParam = null; ParameterInfo curParam = null;
StringBuilder argBuilder = new StringBuilder(input.Length); StringBuilder argBuilder = new StringBuilder(input.Length);
@@ -110,7 +110,7 @@ namespace Discord.Commands
{ {
if (curParam == null) if (curParam == null)
{ {
if (ignoreExtraArgs)
if (command.IgnoreExtraArgs)
break; break;
else else
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters."); return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters.");


+ 44
- 7
src/Discord.Net.Commands/CommandService.cs View File

@@ -6,7 +6,6 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Discord.Commands.Builders; using Discord.Commands.Builders;
using Discord.Logging; using Discord.Logging;


@@ -215,10 +214,11 @@ namespace Discord.Commands
return true; return true;
} }


//Type Readers
//Type Readers
/// <summary> /// <summary>
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type. /// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
/// If <typeparamref name="T"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> will also be added. /// If <typeparamref name="T"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> will also be added.
/// If a default <see cref="TypeReader"/> exists for <typeparamref name="T"/>, a warning will be logged and the default <see cref="TypeReader"/> will be replaced.
/// </summary> /// </summary>
/// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam> /// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param> /// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
@@ -226,17 +226,54 @@ namespace Discord.Commands
=> AddTypeReader(typeof(T), reader); => AddTypeReader(typeof(T), reader);
/// <summary> /// <summary>
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type. /// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
/// If <paramref name="type"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> for the value type will also be added.
/// If <paramref name="type"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> for the value type will also be added.
/// If a default <see cref="TypeReader"/> exists for <paramref name="type"/>, a warning will be logged and the default <see cref="TypeReader"/> will be replaced.
/// </summary> /// </summary>
/// <param name="type">A <see cref="Type"/> instance for the type to be read.</param> /// <param name="type">A <see cref="Type"/> instance for the type to be read.</param>
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param> /// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
public void AddTypeReader(Type type, TypeReader reader) public void AddTypeReader(Type type, TypeReader reader)
{ {
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
readers[reader.GetType()] = reader;
if (_defaultTypeReaders.ContainsKey(type))
_ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." +
$"To suppress this message, use AddTypeReader<T>(reader, true).");
AddTypeReader(type, reader, true);
}
/// <summary>
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
/// If <typeparamref name="T"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> will also be added.
/// </summary>
/// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
/// <param name="replaceDefault">If <paramref name="reader"/> should replace the default <see cref="TypeReader"/> for <typeparamref name="T"/> if one exists.</param>
public void AddTypeReader<T>(TypeReader reader, bool replaceDefault)
=> AddTypeReader(typeof(T), reader, replaceDefault);
/// <summary>
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
/// If <paramref name="type"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> for the value type will also be added.
/// </summary>
/// <param name="type">A <see cref="Type"/> instance for the type to be read.</param>
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
/// <param name="replaceDefault">If <paramref name="reader"/> should replace the default <see cref="TypeReader"/> for <paramref name="type"/> if one exists.</param>
public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault)
{
if (replaceDefault && _defaultTypeReaders.ContainsKey(type))
{
_defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader);
if (type.GetTypeInfo().IsValueType)
{
var nullableType = typeof(Nullable<>).MakeGenericType(type);
var nullableReader = NullableTypeReader.Create(type, reader);
_defaultTypeReaders.AddOrUpdate(nullableType, nullableReader, (k, v) => nullableReader);
}
}
else
{
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
readers[reader.GetType()] = reader;


if (type.GetTypeInfo().IsValueType)
AddNullableTypeReader(type, reader);
if (type.GetTypeInfo().IsValueType)
AddNullableTypeReader(type, reader);
}
} }
internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader)
{ {


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

@@ -20,11 +20,5 @@ namespace Discord.Commands


/// <summary> Determines whether extra parameters should be ignored. </summary> /// <summary> Determines whether extra parameters should be ignored. </summary>
public bool IgnoreExtraArgs { get; set; } = false; public bool IgnoreExtraArgs { get; set; } = false;

///// <summary> Gets or sets the <see cref="IServiceProvider"/> to use. </summary>
//public IServiceProvider ServiceProvider { get; set; } = null;

///// <summary> Gets or sets a factory function for the <see cref="IServiceProvider"/> to use. </summary>
//public Func<CommandService, IServiceProvider> ServiceProviderFactory { get; set; } = null;
} }
} }

+ 6
- 4
src/Discord.Net.Commands/Info/CommandInfo.cs View File

@@ -27,6 +27,7 @@ namespace Discord.Commands
public string Remarks { get; } public string Remarks { get; }
public int Priority { get; } public int Priority { get; }
public bool HasVarArgs { get; } public bool HasVarArgs { get; }
public bool IgnoreExtraArgs { get; }
public RunMode RunMode { get; } public RunMode RunMode { get; }


public IReadOnlyList<string> Aliases { get; } public IReadOnlyList<string> Aliases { get; }
@@ -63,6 +64,7 @@ namespace Discord.Commands


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


_action = builder.Callback; _action = builder.Callback;
_commandService = service; _commandService = service;
@@ -119,7 +121,7 @@ namespace Discord.Commands
return ParseResult.FromError(preconditionResult); return ParseResult.FromError(preconditionResult);


string input = searchResult.Text.Substring(startIndex); string input = searchResult.Text.Substring(startIndex);
return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, 0).ConfigureAwait(false);
return await CommandParser.ParseArgsAsync(this, context, services, input, 0).ConfigureAwait(false);
} }


public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
@@ -165,11 +167,11 @@ namespace Discord.Commands
switch (RunMode) switch (RunMode)
{ {
case RunMode.Sync: //Always sync case RunMode.Sync: //Always sync
return await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false);
return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
case RunMode.Async: //Always async case RunMode.Async: //Always async
var t2 = Task.Run(async () => var t2 = Task.Run(async () =>
{ {
await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false);
await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
}); });
break; break;
} }
@@ -181,7 +183,7 @@ namespace Discord.Commands
} }
} }


private async Task<IResult> ExecuteAsyncInternalAsync(ICommandContext context, object[] args, IServiceProvider services)
private async Task<IResult> ExecuteInternalAsync(ICommandContext context, object[] args, IServiceProvider services)
{ {
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
try try


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

@@ -2,7 +2,6 @@ using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Reflection;
using Discord.Commands.Builders; using Discord.Commands.Builders;


namespace Discord.Commands namespace Discord.Commands
@@ -23,8 +22,6 @@ namespace Discord.Commands
public ModuleInfo Parent { get; } public ModuleInfo Parent { get; }
public bool IsSubmodule => Parent != null; public bool IsSubmodule => Parent != null;


//public TypeInfo TypeInfo { get; }

internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null) internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null)
{ {
Service = service; Service = service;
@@ -35,8 +32,6 @@ namespace Discord.Commands
Group = builder.Group; Group = builder.Group;
Parent = parent; Parent = parent;


//TypeInfo = builder.TypeInfo;

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


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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@@ -6,7 +6,7 @@ using System.Threading.Tasks;


namespace Discord.Commands namespace Discord.Commands
{ {
internal class ChannelTypeReader<T> : TypeReader
public class ChannelTypeReader<T> : TypeReader
where T : class, IChannel where T : class, IChannel
{ {
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)


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

@@ -1,10 +1,10 @@
using System;
using System;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Commands namespace Discord.Commands
{ {
internal class MessageTypeReader<T> : TypeReader
public class MessageTypeReader<T> : TypeReader
where T : class, IMessage where T : class, IMessage
{ {
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)


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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@@ -6,7 +6,7 @@ using System.Threading.Tasks;


namespace Discord.Commands namespace Discord.Commands
{ {
internal class RoleTypeReader<T> : TypeReader
public class RoleTypeReader<T> : TypeReader
where T : class, IRole where T : class, IRole
{ {
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)


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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Globalization; using System.Globalization;
@@ -7,7 +7,7 @@ using System.Threading.Tasks;


namespace Discord.Commands namespace Discord.Commands
{ {
internal class UserTypeReader<T> : TypeReader
public class UserTypeReader<T> : TypeReader
where T : class, IUser where T : class, IUser
{ {
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)


+ 5
- 1
src/Discord.Net.Commands/Results/TypeReaderResult.cs View File

@@ -1,7 +1,8 @@
using System;
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;


namespace Discord.Commands namespace Discord.Commands
{ {
@@ -30,6 +31,9 @@ namespace Discord.Commands
public string ErrorReason { get; } public string ErrorReason { get; }


public bool IsSuccess => !Error.HasValue; public bool IsSuccess => !Error.HasValue;
public object BestMatch => IsSuccess
? (Values.Count == 1 ? Values.Single().Value : Values.OrderByDescending(v => v.Score).First().Value)
: throw new InvalidOperationException("TypeReaderResult was not successful.");


private TypeReaderResult(IReadOnlyCollection<TypeReaderValue> values, CommandError? error, string errorReason) private TypeReaderResult(IReadOnlyCollection<TypeReaderValue> values, CommandError? error, string errorReason)
{ {


+ 16
- 5
src/Discord.Net.Core/CDN.cs View File

@@ -13,6 +13,10 @@ namespace Discord
string extension = FormatToExtension(format, avatarId); string extension = FormatToExtension(format, avatarId);
return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}"; return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}";
} }
public static string GetDefaultUserAvatarUrl(ushort discriminator)
{
return $"{DiscordConfig.CDNUrl}embed/avatars/{discriminator % 5}.png";
}
public static string GetGuildIconUrl(ulong guildId, string iconId) public static string GetGuildIconUrl(ulong guildId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null; => iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null;
public static string GetGuildSplashUrl(ulong guildId, string splashId) public static string GetGuildSplashUrl(ulong guildId, string splashId)
@@ -30,6 +34,8 @@ namespace Discord


public static string GetSpotifyAlbumArtUrl(string albumArtId) public static string GetSpotifyAlbumArtUrl(string albumArtId)
=> $"https://i.scdn.co/image/{albumArtId}"; => $"https://i.scdn.co/image/{albumArtId}";
public static string GetSpotifyDirectUrl(string trackId)
=> $"https://open.spotify.com/track/{trackId}";


private static string FormatToExtension(ImageFormat format, string imageId) private static string FormatToExtension(ImageFormat format, string imageId)
{ {
@@ -37,11 +43,16 @@ namespace Discord
format = imageId.StartsWith("a_") ? ImageFormat.Gif : ImageFormat.Png; format = imageId.StartsWith("a_") ? ImageFormat.Gif : ImageFormat.Png;
switch (format) switch (format)
{ {
case ImageFormat.Gif: return "gif";
case ImageFormat.Jpeg: return "jpeg";
case ImageFormat.Png: return "png";
case ImageFormat.WebP: return "webp";
default: throw new ArgumentException(nameof(format));
case ImageFormat.Gif:
return "gif";
case ImageFormat.Jpeg:
return "jpeg";
case ImageFormat.Png:
return "png";
case ImageFormat.WebP:
return "webp";
default:
throw new ArgumentException(nameof(format));
} }
} }
} }


+ 3
- 2
src/Discord.Net.Core/Discord.Net.Core.csproj View File

@@ -4,11 +4,12 @@
<AssemblyName>Discord.Net.Core</AssemblyName> <AssemblyName>Discord.Net.Core</AssemblyName>
<RootNamespace>Discord</RootNamespace> <RootNamespace>Discord</RootNamespace>
<Description>The core components for the Discord.Net library.</Description> <Description>The core components for the Discord.Net library.</Description>
<TargetFrameworks>net45;netstandard1.1;netstandard1.3</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net45;netstandard1.1;netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.1;netstandard1.3;netstandard2.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> <PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
<PackageReference Include="System.Interactive.Async" Version="3.1.1" /> <PackageReference Include="System.Interactive.Async" Version="3.1.1" />
</ItemGroup> </ItemGroup>
</Project>
</Project>

+ 6
- 4
src/Discord.Net.Core/DiscordConfig.cs View File

@@ -1,13 +1,13 @@
using System.Reflection;
using System.Reflection;


namespace Discord namespace Discord
{ {
public class DiscordConfig public class DiscordConfig
{ {
public const int APIVersion = 6;
public const int APIVersion = 6;
public static string Version { get; } = public static string Version { get; } =
typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ??
typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ??
typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ??
"Unknown"; "Unknown";


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})";
@@ -20,10 +20,12 @@ namespace Discord
public const int MaxMessagesPerBatch = 100; public const int MaxMessagesPerBatch = 100;
public const int MaxUsersPerBatch = 1000; public const int MaxUsersPerBatch = 1000;
public const int MaxGuildsPerBatch = 100; public const int MaxGuildsPerBatch = 100;
public const int MaxUserReactionsPerBatch = 100;
public const int MaxAuditLogEntriesPerBatch = 100;


/// <summary> Gets or sets how a request should act in the case of an error, by default. </summary> /// <summary> Gets or sets how a request should act in the case of an error, by default. </summary>
public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry;
/// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> /// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary>
public LogSeverity LogLevel { get; set; } = LogSeverity.Info; public LogSeverity LogLevel { get; set; } = LogSeverity.Info;




+ 8
- 5
src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs View File

@@ -7,17 +7,20 @@ namespace Discord
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SpotifyGame : Game public class SpotifyGame : Game
{ {
public IEnumerable<string> Artists { get; internal set; }
public string AlbumArt { get; internal set; }
public IReadOnlyCollection<string> Artists { get; internal set; }
public string AlbumTitle { get; internal set; } public string AlbumTitle { get; internal set; }
public string TrackTitle { get; internal set; } public string TrackTitle { get; internal set; }
public string SyncId { get; internal set; }
public string SessionId { get; internal set; }
public TimeSpan? Duration { get; internal set; } public TimeSpan? Duration { get; internal set; }


public string TrackId { get; internal set; }
public string SessionId { get; internal set; }

public string AlbumArtUrl { get; internal set; }
public string TrackUrl { get; internal set; }

internal SpotifyGame() { } internal SpotifyGame() { }


public override string ToString() => Name;
public override string ToString() => $"{string.Join(", ", Artists)} - {TrackTitle} ({Duration})";
private string DebuggerDisplay => $"{Name} (Spotify)"; private string DebuggerDisplay => $"{Name} (Spotify)";
} }
} }

+ 50
- 0
src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs View File

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

namespace Discord
{
/// <summary>
/// The action type within a <see cref="IAuditLogEntry"/>
/// </summary>
public enum ActionType
{
GuildUpdated = 1,

ChannelCreated = 10,
ChannelUpdated = 11,
ChannelDeleted = 12,

OverwriteCreated = 13,
OverwriteUpdated = 14,
OverwriteDeleted = 15,

Kick = 20,
Prune = 21,
Ban = 22,
Unban = 23,

MemberUpdated = 24,
MemberRoleUpdated = 25,

RoleCreated = 30,
RoleUpdated = 31,
RoleDeleted = 32,

InviteCreated = 40,
InviteUpdated = 41,
InviteDeleted = 42,

WebhookCreated = 50,
WebhookUpdated = 51,
WebhookDeleted = 52,

EmojiCreated = 60,
EmojiUpdated = 61,
EmojiDeleted = 62,

MessageDeleted = 72
}
}

+ 14
- 0
src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs View File

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

namespace Discord
{
/// <summary>
/// Represents data applied to an <see cref="IAuditLogEntry"/>
/// </summary>
public interface IAuditLogData
{ }
}

+ 34
- 0
src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs View File

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

namespace Discord
{
/// <summary>
/// Represents an entry in an audit log
/// </summary>
public interface IAuditLogEntry : IEntity<ulong>
{
/// <summary>
/// The action which occured to create this entry
/// </summary>
ActionType Action { get; }

/// <summary>
/// The data for this entry. May be <see cref="null"/> if no data was available.
/// </summary>
IAuditLogData Data { get; }

/// <summary>
/// The user responsible for causing the changes
/// </summary>
IUser User { get; }

/// <summary>
/// The reason behind the change. May be <see cref="null"/> if no reason was provided.
/// </summary>
string Reason { get; }
}
}

+ 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, Embed embed = null, RequestOptions options = null);
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null);
#if FILESYSTEM #if FILESYSTEM
/// <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, Embed embed = null, RequestOptions options = null); Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null);


+ 26
- 4
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -1,4 +1,4 @@
using Discord.Audio;
using Discord.Audio;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -66,6 +66,24 @@ namespace Discord


/// <summary> Gets a collection of all users banned on this guild. </summary> /// <summary> Gets a collection of all users banned on this guild. </summary>
Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null); Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null);
/// <summary>
/// Gets a ban object for a banned user.
/// </summary>
/// <param name="user">The banned user.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing the ban object, which contains the user information and the
/// reason for the ban; <see langword="null"/> if the ban entry cannot be found.
/// </returns>
Task<IBan> GetBanAsync(IUser user, RequestOptions options = null);
/// <summary>
/// Gets a ban object for a banned user.
/// </summary>
/// <param name="userId">The snowflake identifier for the banned user.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing the ban object, which contains the user information and the
/// reason for the ban; <see langword="null"/> if the ban entry cannot be found.
/// </returns>
Task<IBan> GetBanAsync(ulong userId, RequestOptions options = null);
/// <summary> Bans the provided user from this guild and optionally prunes their recent messages. </summary> /// <summary> Bans the provided user from this guild and optionally prunes their recent messages. </summary>
/// <param name="pruneDays">The number of days to remove messages from this user for - must be between [0, 7]</param> /// <param name="pruneDays">The number of days to remove messages from this user for - must be between [0, 7]</param>
Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null);
@@ -91,9 +109,9 @@ namespace Discord
Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> Creates a new text channel. </summary> /// <summary> Creates a new text channel. </summary>
Task<ITextChannel> CreateTextChannelAsync(string name, RequestOptions options = null);
Task<ITextChannel> CreateTextChannelAsync(string name, Action<TextChannelProperties> func = null, RequestOptions options = null);
/// <summary> Creates a new voice channel. </summary> /// <summary> Creates a new voice channel. </summary>
Task<IVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null);
Task<IVoiceChannel> CreateVoiceChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null);
/// <summary> Creates a new channel category. </summary> /// <summary> Creates a new channel category. </summary>
Task<ICategoryChannel> CreateCategoryAsync(string name, RequestOptions options = null); Task<ICategoryChannel> CreateCategoryAsync(string name, RequestOptions options = null);


@@ -121,6 +139,10 @@ namespace Discord
/// <summary> Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. </summary> /// <summary> Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. </summary>
Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null);


/// <summary> Gets the specified number of audit log entries for this guild. </summary>
Task<IReadOnlyCollection<IAuditLogEntry>> GetAuditLogAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch,
CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);

/// <summary> Gets the webhook in this guild with the provided id, or null if not found. </summary> /// <summary> Gets the webhook in this guild with the provided id, or null if not found. </summary>
Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null); Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null);
/// <summary> Gets a collection of all webhooks for this guild. </summary> /// <summary> Gets a collection of all webhooks for this guild. </summary>
@@ -135,4 +157,4 @@ namespace Discord
/// <summary> Deletes an existing emote from this guild. </summary> /// <summary> Deletes an existing emote from this guild. </summary>
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null);
} }
}
}

+ 6
- 6
src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs View File

@@ -1,4 +1,4 @@
namespace Discord
namespace Discord
{ {
public interface IVoiceRegion public interface IVoiceRegion
{ {
@@ -10,9 +10,9 @@
bool IsVip { get; } bool IsVip { get; }
/// <summary> Returns true if this voice region is the closest to your machine. </summary> /// <summary> Returns true if this voice region is the closest to your machine. </summary>
bool IsOptimal { get; } bool IsOptimal { get; }
/// <summary> Gets an example hostname for this voice region. </summary>
string SampleHostname { get; }
/// <summary> Gets an example port for this voice region. </summary>
int SamplePort { get; }
/// <summary> Returns true if this is a deprecated voice region (avoid switching to these). </summary>
bool IsDeprecated { get; }
/// <summary> Returns true if this is a custom voice region (used for events/etc) </summary>
bool IsCustom { get; }
} }
}
}

+ 5
- 4
src/Discord.Net.Core/Entities/Invites/IInvite.cs View File

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


namespace Discord namespace Discord
{ {
@@ -22,8 +22,9 @@ namespace Discord
ulong GuildId { get; } ulong GuildId { get; }
/// <summary> Gets the name of the guild this invite is linked to. </summary> /// <summary> Gets the name of the guild this invite is linked to. </summary>
string GuildName { get; } string GuildName { get; }

/// <summary> Accepts this invite and joins the target guild. This will fail on bot accounts. </summary>
Task AcceptAsync(RequestOptions options = null);
/// <summary> Gets the approximated count of online members in the guild. </summary>
int? PresenceCount { get; }
/// <summary> Gets the approximated count of total members in the guild. </summary>
int? MemberCount { get; }
} }
} }

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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@@ -57,7 +57,18 @@ namespace Discord
Fields = fields; Fields = fields;
} }


public int Length => Title?.Length + Author?.Name?.Length + Description?.Length + Footer?.Text?.Length + Fields.Sum(f => f.Name.Length + f.Value.ToString().Length) ?? 0;
public int Length
{
get
{
int titleLength = Title?.Length ?? 0;
int authorLength = Author?.Name?.Length ?? 0;
int descriptionLength = Description?.Length ?? 0;
int footerLength = Footer?.Text?.Length ?? 0;
int fieldSum = Fields.Sum(f => f.Name?.Length + f.Value?.ToString().Length) ?? 0;
return titleLength + authorLength + descriptionLength + footerLength + fieldSum;
}
}


public override string ToString() => Title; public override string ToString() => Title;
private string DebuggerDisplay => $"{Title} ({Type})"; private string DebuggerDisplay => $"{Title} ({Type})";


+ 68
- 71
src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs View File

@@ -1,89 +1,105 @@
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;


namespace Discord namespace Discord
{ {
public class EmbedBuilder public class EmbedBuilder
{ {
private readonly Embed _embed;
private string _title;
private string _description;
private string _url;
private EmbedImage? _image;
private EmbedThumbnail? _thumbnail;
private List<EmbedFieldBuilder> _fields;


public const int MaxFieldCount = 25; public const int MaxFieldCount = 25;
public const int MaxTitleLength = 256; public const int MaxTitleLength = 256;
public const int MaxDescriptionLength = 2048; public const int MaxDescriptionLength = 2048;
public const int MaxEmbedLength = 6000; // user bot limit is 2000, but we don't validate that here.
public const int MaxEmbedLength = 6000;


public EmbedBuilder() public EmbedBuilder()
{ {
_embed = new Embed(EmbedType.Rich);
Fields = new List<EmbedFieldBuilder>(); Fields = new List<EmbedFieldBuilder>();
} }


public string Title public string Title
{ {
get => _embed.Title;
get => _title;
set set
{ {
if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title)); if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title));
_embed.Title = value;
_title = value;
} }
} }

public string Description public string Description
{ {
get => _embed.Description;
get => _description;
set set
{ {
if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description));
_embed.Description = value;
_description = value;
} }
} }


public string Url public string Url
{ {
get => _embed.Url;
get => _url;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url));
_embed.Url = value;
_url = value;
} }
} }
public string ThumbnailUrl public string ThumbnailUrl
{ {
get => _embed.Thumbnail?.Url;
get => _thumbnail?.Url;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl));
_embed.Thumbnail = new EmbedThumbnail(value, null, null, null);
_thumbnail = new EmbedThumbnail(value, null, null, null);
} }
} }
public string ImageUrl public string ImageUrl
{ {
get => _embed.Image?.Url;
get => _image?.Url;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl));
_embed.Image = new EmbedImage(value, null, null, null);
_image = new EmbedImage(value, null, null, null);
} }
} }
public DateTimeOffset? Timestamp { get => _embed.Timestamp; set { _embed.Timestamp = value; } }
public Color? Color { get => _embed.Color; set { _embed.Color = value; } }

public EmbedAuthorBuilder Author { get; set; }
public EmbedFooterBuilder Footer { get; set; }
private List<EmbedFieldBuilder> _fields;
public List<EmbedFieldBuilder> Fields public List<EmbedFieldBuilder> Fields
{ {
get => _fields; get => _fields;
set set
{ {

if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields));
if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields));
_fields = value; _fields = value;
} }
} }


public DateTimeOffset? Timestamp { get; set; }
public Color? Color { get; set; }
public EmbedAuthorBuilder Author { get; set; }
public EmbedFooterBuilder Footer { get; set; }

public int Length
{
get
{
int titleLength = Title?.Length ?? 0;
int authorLength = Author?.Name?.Length ?? 0;
int descriptionLength = Description?.Length ?? 0;
int footerLength = Footer?.Text?.Length ?? 0;
int fieldSum = Fields.Sum(f => f.Name.Length + f.Value.ToString().Length);

return titleLength + authorLength + descriptionLength + footerLength + fieldSum;
}
}

public EmbedBuilder WithTitle(string title) public EmbedBuilder WithTitle(string title)
{ {
Title = title; Title = title;
@@ -180,7 +196,6 @@ namespace Discord
AddField(field); AddField(field);
return this; return this;
} }

public EmbedBuilder AddField(EmbedFieldBuilder field) public EmbedBuilder AddField(EmbedFieldBuilder field)
{ {
if (Fields.Count >= MaxFieldCount) if (Fields.Count >= MaxFieldCount)
@@ -195,63 +210,53 @@ namespace Discord
{ {
var field = new EmbedFieldBuilder(); var field = new EmbedFieldBuilder();
action(field); action(field);
this.AddField(field);
AddField(field);
return this; return this;
} }


public Embed Build() public Embed Build()
{ {
_embed.Footer = Footer?.Build();
_embed.Author = Author?.Build();
if (Length > MaxEmbedLength)
throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}");

var fields = ImmutableArray.CreateBuilder<EmbedField>(Fields.Count); var fields = ImmutableArray.CreateBuilder<EmbedField>(Fields.Count);
for (int i = 0; i < Fields.Count; i++) for (int i = 0; i < Fields.Count; i++)
fields.Add(Fields[i].Build()); fields.Add(Fields[i].Build());
_embed.Fields = fields.ToImmutable();


if (_embed.Length > MaxEmbedLength)
{
throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}");
}

return _embed;
return new Embed(EmbedType.Rich, Title, Description, Url, Timestamp, Color, _image, null, Author?.Build(), Footer?.Build(), null, _thumbnail, fields.ToImmutable());
} }
} }


public class EmbedFieldBuilder public class EmbedFieldBuilder
{ {
private EmbedField _field;
private string _name;
private string _value;
public const int MaxFieldNameLength = 256; public const int MaxFieldNameLength = 256;
public const int MaxFieldValueLength = 1024; public const int MaxFieldValueLength = 1024;


public string Name public string Name
{ {
get => _field.Name;
get => _name;
set set
{ {
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name));
if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name));
_field.Name = value;
_name = value;
} }
} }


public object Value public object Value
{ {
get => _field.Value;
get => _value;
set set
{ {
var stringValue = value?.ToString(); var stringValue = value?.ToString();
if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value));
if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value));
_field.Value = stringValue;
_value = stringValue;
} }
} }
public bool IsInline { get => _field.Inline; set { _field.Inline = value; } }

public EmbedFieldBuilder()
{
_field = new EmbedField();
}
public bool IsInline { get; set; }


public EmbedFieldBuilder WithName(string name) public EmbedFieldBuilder WithName(string name)
{ {
@@ -270,48 +275,44 @@ namespace Discord
} }


public EmbedField Build() public EmbedField Build()
=> _field;
=> new EmbedField(Name, Value.ToString(), IsInline);
} }


public class EmbedAuthorBuilder public class EmbedAuthorBuilder
{ {
private EmbedAuthor _author;

private string _name;
private string _url;
private string _iconUrl;
public const int MaxAuthorNameLength = 256; public const int MaxAuthorNameLength = 256;


public string Name public string Name
{ {
get => _author.Name;
get => _name;
set set
{ {
if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name));
_author.Name = value;
_name = value;
} }
} }
public string Url public string Url
{ {
get => _author.Url;
get => _url;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url));
_author.Url = value;
_url = value;
} }
} }
public string IconUrl public string IconUrl
{ {
get => _author.IconUrl;
get => _iconUrl;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl));
_author.IconUrl = value;
_iconUrl = value;
} }
} }


public EmbedAuthorBuilder()
{
_author = new EmbedAuthor();
}

public EmbedAuthorBuilder WithName(string name) public EmbedAuthorBuilder WithName(string name)
{ {
Name = name; Name = name;
@@ -329,39 +330,35 @@ namespace Discord
} }


public EmbedAuthor Build() public EmbedAuthor Build()
=> _author;
=> new EmbedAuthor(Name, Url, IconUrl, null);
} }


public class EmbedFooterBuilder public class EmbedFooterBuilder
{ {
private EmbedFooter _footer;
private string _text;
private string _iconUrl;


public const int MaxFooterTextLength = 2048; public const int MaxFooterTextLength = 2048;


public string Text public string Text
{ {
get => _footer.Text;
get => _text;
set set
{ {
if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text));
_footer.Text = value;
_text = value;
} }
} }
public string IconUrl public string IconUrl
{ {
get => _footer.IconUrl;
get => _iconUrl;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl));
_footer.IconUrl = value;
_iconUrl = value;
} }
} }


public EmbedFooterBuilder()
{
_footer = new EmbedFooter();
}

public EmbedFooterBuilder WithText(string text) public EmbedFooterBuilder WithText(string text)
{ {
Text = text; Text = text;
@@ -374,6 +371,6 @@ namespace Discord
} }


public EmbedFooter Build() public EmbedFooter Build()
=> _footer;
=> new EmbedFooter(Text, IconUrl, null);
} }
} }

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

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


@@ -23,7 +23,7 @@ namespace Discord
/// <summary> Removes all reactions from this message. </summary> /// <summary> Removes all reactions from this message. </summary>
Task RemoveAllReactionsAsync(RequestOptions options = null); Task RemoveAllReactionsAsync(RequestOptions options = null);
/// <summary> Gets all users that reacted to a message with a given emote </summary> /// <summary> Gets all users that reacted to a message with a given emote </summary>
Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emoji, int limit = 100, ulong? afterUserId = null, RequestOptions options = null);
IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emoji, int limit, RequestOptions options = null);


/// <summary> Transforms this message's text into a human readable form by resolving its tags. </summary> /// <summary> Transforms this message's text into a human readable form by resolving its tags. </summary>
string Resolve( string Resolve(


+ 6
- 3
src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;


@@ -12,7 +12,9 @@ namespace Discord
/// <summary> Gets a ChannelPermissions that grants all permissions for text channels. </summary> /// <summary> Gets a ChannelPermissions that grants all permissions for text channels. </summary>
public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001);
/// <summary> Gets a ChannelPermissions that grants all permissions for voice channels. </summary> /// <summary> Gets a ChannelPermissions that grants all permissions for voice channels. </summary>
public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001);
public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010000_010001);
/// <summary> Gets a ChannelPermissions that grants all permissions for category channels. </summary>
public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001);
/// <summary> Gets a ChannelPermissions that grants all permissions for direct message channels. </summary> /// <summary> Gets a ChannelPermissions that grants all permissions for direct message channels. </summary>
public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000);
/// <summary> Gets a ChannelPermissions that grants all permissions for group channels. </summary> /// <summary> Gets a ChannelPermissions that grants all permissions for group channels. </summary>
@@ -24,6 +26,7 @@ namespace Discord
{ {
case ITextChannel _: return Text; case ITextChannel _: return Text;
case IVoiceChannel _: return Voice; case IVoiceChannel _: return Voice;
case ICategoryChannel _: return Category;
case IDMChannel _: return DM; case IDMChannel _: return DM;
case IGroupChannel _: return Group; case IGroupChannel _: return Group;
default: throw new ArgumentException("Unknown channel type", nameof(channel)); default: throw new ArgumentException("Unknown channel type", nameof(channel));
@@ -157,4 +160,4 @@ namespace Discord
public override string ToString() => RawValue.ToString(); public override string ToString() => RawValue.ToString();
private string DebuggerDisplay => $"{string.Join(", ", ToList())}"; private string DebuggerDisplay => $"{string.Join(", ", ToList())}";
} }
}
}

+ 11
- 1
src/Discord.Net.Core/Entities/Roles/Color.cs View File

@@ -1,5 +1,8 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
#if NETSTANDARD2_0 || NET45
using StandardColor = System.Drawing.Color;
#endif


namespace Discord namespace Discord
{ {
@@ -96,7 +99,14 @@ namespace Discord
((uint)(g * 255.0f) << 8) | ((uint)(g * 255.0f) << 8) |
(uint)(b * 255.0f); (uint)(b * 255.0f);
} }

#if NETSTANDARD2_0 || NET45
public static implicit operator StandardColor(Color color) =>
StandardColor.FromArgb((int)color.RawValue);
public static explicit operator Color(StandardColor color) =>
new Color((uint)color.ToArgb() << 8 >> 8);
#endif

public override string ToString() => public override string ToString() =>
$"#{Convert.ToString(RawValue, 16)}"; $"#{Convert.ToString(RawValue, 16)}";
private string DebuggerDisplay => private string DebuggerDisplay =>


+ 2
- 0
src/Discord.Net.Core/Entities/Users/IUser.cs View File

@@ -8,6 +8,8 @@ namespace Discord
string AvatarId { get; } string AvatarId { get; }
/// <summary> Gets the url to this user's avatar. </summary> /// <summary> Gets the url to this user's avatar. </summary>
string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128);
/// <summary> Gets the url to this user's default avatar. </summary>
string GetDefaultAvatarUrl();
/// <summary> Gets the per-username unique id for this user. </summary> /// <summary> Gets the per-username unique id for this user. </summary>
string Discriminator { get; } string Discriminator { get; }
/// <summary> Gets the per-username unique id for this user. </summary> /// <summary> Gets the per-username unique id for this user. </summary>


+ 14
- 0
src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs View File

@@ -0,0 +1,14 @@
namespace Discord
{
/// <summary>
/// Represents the type of a webhook.
/// </summary>
/// <remarks>
/// This type is currently unused, and is only returned in audit log responses.
/// </remarks>
public enum WebhookType
{
/// <summary> An incoming webhook </summary>
Incoming = 1
}
}

+ 1
- 1
src/Discord.Net.Core/Extensions/UserExtensions.cs View File

@@ -9,7 +9,7 @@ namespace Discord
/// Sends a message to the user via DM. /// Sends a message to the user via DM.
/// </summary> /// </summary>
public static async Task<IUserMessage> SendMessageAsync(this IUser user, public static async Task<IUserMessage> SendMessageAsync(this IUser user,
string text,
string text = null,
bool isTTS = false, bool isTTS = false,
Embed embed = null, Embed embed = null,
RequestOptions options = null) RequestOptions options = null)


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

@@ -27,7 +27,7 @@ namespace Discord
Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null);
Task<IInvite> GetInviteAsync(string inviteId, RequestOptions options = null);
Task<IInvite> GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null);


Task<IUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
Task<IUser> GetUserAsync(string username, string discriminator, RequestOptions options = null); Task<IUser> GetUserAsync(string username, string discriminator, RequestOptions options = null);


+ 3
- 2
src/Discord.Net.DebugTools/Discord.Net.DebugTools.csproj View File

@@ -4,9 +4,10 @@
<AssemblyName>Discord.Net.DebugTools</AssemblyName> <AssemblyName>Discord.Net.DebugTools</AssemblyName>
<RootNamespace>Discord</RootNamespace> <RootNamespace>Discord</RootNamespace>
<Description>A Discord.Net extension adding some helper classes for diagnosing issues.</Description> <Description>A Discord.Net extension adding some helper classes for diagnosing issues.</Description>
<TargetFrameworks>net45;netstandard1.3</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net45;netstandard1.3</TargetFrameworks>
<TargetFramework Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.3</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
</ItemGroup> </ItemGroup>
</Project>
</Project>

+ 16
- 0
src/Discord.Net.Rest/API/Common/AuditLog.cs View File

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

namespace Discord.API
{
internal class AuditLog
{
[JsonProperty("webhooks")]
public Webhook[] Webhooks { get; set; }

[JsonProperty("users")]
public User[] Users { get; set; }

[JsonProperty("audit_log_entries")]
public AuditLogEntry[] Entries { get; set; }
}
}

+ 17
- 0
src/Discord.Net.Rest/API/Common/AuditLogChange.cs View File

@@ -0,0 +1,17 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Discord.API
{
internal class AuditLogChange
{
[JsonProperty("key")]
public string ChangedProperty { get; set; }

[JsonProperty("new_value")]
public JToken NewValue { get; set; }

[JsonProperty("old_value")]
public JToken OldValue { get; set; }
}
}

+ 26
- 0
src/Discord.Net.Rest/API/Common/AuditLogEntry.cs View File

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

namespace Discord.API
{
internal class AuditLogEntry
{
[JsonProperty("target_id")]
public ulong? TargetId { get; set; }
[JsonProperty("user_id")]
public ulong UserId { get; set; }

[JsonProperty("changes")]
public AuditLogChange[] Changes { get; set; }
[JsonProperty("options")]
public AuditLogOptions Options { get; set; }

[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("action_type")]
public ActionType Action { get; set; }

[JsonProperty("reason")]
public string Reason { get; set; }
}
}

+ 27
- 0
src/Discord.Net.Rest/API/Common/AuditLogOptions.cs View File

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

namespace Discord.API
{
internal class AuditLogOptions
{
//Message delete
[JsonProperty("count")]
public int? MessageDeleteCount { get; set; }
[JsonProperty("channel_id")]
public ulong? MessageDeleteChannelId { get; set; }

//Prune
[JsonProperty("delete_member_days")]
public int? PruneDeleteMemberDays { get; set; }
[JsonProperty("members_removed")]
public int? PruneMembersRemoved { get; set; }

//Overwrite Update
[JsonProperty("role_name")]
public string OverwriteRoleName { get; set; }
[JsonProperty("type")]
public string OverwriteType { get; set; }
[JsonProperty("id")]
public ulong? OverwriteTargetId { get; set; }
}
}

+ 5
- 1
src/Discord.Net.Rest/API/Common/Invite.cs View File

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


namespace Discord.API namespace Discord.API
@@ -11,5 +11,9 @@ namespace Discord.API
public InviteGuild Guild { get; set; } public InviteGuild Guild { get; set; }
[JsonProperty("channel")] [JsonProperty("channel")]
public InviteChannel Channel { get; set; } public InviteChannel Channel { get; set; }
[JsonProperty("approximate_presence_count")]
public Optional<int?> PresenceCount { get; set; }
[JsonProperty("approximate_member_count")]
public Optional<int?> MemberCount { get; set; }
} }
} }

+ 5
- 5
src/Discord.Net.Rest/API/Common/VoiceRegion.cs View File

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


namespace Discord.API namespace Discord.API
@@ -13,9 +13,9 @@ namespace Discord.API
public bool IsVip { get; set; } public bool IsVip { get; set; }
[JsonProperty("optimal")] [JsonProperty("optimal")]
public bool IsOptimal { get; set; } public bool IsOptimal { get; set; }
[JsonProperty("sample_hostname")]
public string SampleHostname { get; set; }
[JsonProperty("sample_port")]
public int SamplePort { get; set; }
[JsonProperty("deprecated")]
public bool IsDeprecated { get; set; }
[JsonProperty("custom")]
public bool IsCustom { get; set; }
} }
} }

+ 12
- 1
src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs View File

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


namespace Discord.API.Rest namespace Discord.API.Rest
@@ -10,9 +10,20 @@ namespace Discord.API.Rest
public string Name { get; } public string Name { get; }
[JsonProperty("type")] [JsonProperty("type")]
public ChannelType Type { get; } public ChannelType Type { get; }
[JsonProperty("parent_id")]
public Optional<ulong?> CategoryId { get; set; }


//Text channels
[JsonProperty("topic")]
public Optional<string> Topic { get; set; }
[JsonProperty("nsfw")]
public Optional<bool> IsNsfw { get; set; }

//Voice channels
[JsonProperty("bitrate")] [JsonProperty("bitrate")]
public Optional<int> Bitrate { get; set; } public Optional<int> Bitrate { get; set; }
[JsonProperty("user_limit")]
public Optional<int?> UserLimit { get; set; }


public CreateGuildChannelParams(string name, ChannelType type) public CreateGuildChannelParams(string name, ChannelType type)
{ {


+ 8
- 0
src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs View File

@@ -0,0 +1,8 @@
namespace Discord.API.Rest
{
class GetAuditLogsParams
{
public Optional<int> Limit { get; set; }
public Optional<ulong> BeforeEntryId { get; set; }
}
}

+ 7
- 0
src/Discord.Net.Rest/API/Rest/GetInviteParams.cs View File

@@ -0,0 +1,7 @@
namespace Discord.API.Rest
{
internal class GetInviteParams
{
public Optional<bool?> WithCounts { get; set; }
}
}

+ 1
- 1
src/Discord.Net.Rest/API/Rest/GetReactionUsersParams.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Rest
namespace Discord.API.Rest
{ {
internal class GetReactionUsersParams internal class GetReactionUsersParams
{ {


+ 12
- 0
src/Discord.Net.Rest/AssemblyInfo.cs View File

@@ -6,5 +6,17 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Discord.Net.Commands")] [assembly: InternalsVisibleTo("Discord.Net.Commands")]
[assembly: InternalsVisibleTo("Discord.Net.Tests")] [assembly: InternalsVisibleTo("Discord.Net.Tests")]


[assembly: TypeForwardedTo(typeof(Discord.Embed))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedBuilder))] [assembly: TypeForwardedTo(typeof(Discord.EmbedBuilder))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedBuilderExtensions))] [assembly: TypeForwardedTo(typeof(Discord.EmbedBuilderExtensions))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedAuthor))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedAuthorBuilder))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedField))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedFieldBuilder))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedFooter))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedFooterBuilder))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedImage))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedProvider))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedThumbnail))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedType))]
[assembly: TypeForwardedTo(typeof(Discord.EmbedVideo))]

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

@@ -148,7 +148,7 @@ namespace Discord.Rest
Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IConnection>>(ImmutableArray.Create<IConnection>()); => Task.FromResult<IReadOnlyCollection<IConnection>>(ImmutableArray.Create<IConnection>());


Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options)
Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options)
=> Task.FromResult<IInvite>(null); => Task.FromResult<IInvite>(null);


Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options)


+ 8
- 4
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -50,12 +50,16 @@ namespace Discord.Rest
return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); return models.Select(x => RestConnection.Create(x)).ToImmutableArray();
} }
public static async Task<RestInvite> GetInviteAsync(BaseDiscordClient client,
string inviteId, RequestOptions options)
public static async Task<RestInviteMetadata> GetInviteAsync(BaseDiscordClient client,
string inviteId, bool withCount, RequestOptions options)
{ {
var model = await client.ApiClient.GetInviteAsync(inviteId, options).ConfigureAwait(false);
var args = new GetInviteParams
{
WithCounts = withCount
};
var model = await client.ApiClient.GetInviteAsync(inviteId, args, options).ConfigureAwait(false);
if (model != null) if (model != null)
return RestInvite.Create(client, null, null, model);
return RestInviteMetadata.Create(client, null, null, model);
return null; return null;
} }


+ 3
- 2
src/Discord.Net.Rest/Discord.Net.Rest.csproj View File

@@ -4,7 +4,8 @@
<AssemblyName>Discord.Net.Rest</AssemblyName> <AssemblyName>Discord.Net.Rest</AssemblyName>
<RootNamespace>Discord.Rest</RootNamespace> <RootNamespace>Discord.Rest</RootNamespace>
<Description>A core Discord.Net library containing the REST client and models.</Description> <Description>A core Discord.Net library containing the REST client and models.</Description>
<TargetFrameworks>net45;netstandard1.1;netstandard1.3</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net45;netstandard1.1;netstandard1.3</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.1;netstandard1.3</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
@@ -16,4 +17,4 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
</Project>
</Project>

+ 36
- 12
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -618,15 +618,15 @@ namespace Discord.API
Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); Preconditions.NotNullOrWhitespace(emoji, nameof(emoji));
Preconditions.NotNull(args, nameof(args)); Preconditions.NotNull(args, nameof(args));
Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit));
Preconditions.AtMost(args.Limit, DiscordConfig.MaxUsersPerBatch, nameof(args.Limit));
Preconditions.AtMost(args.Limit, DiscordConfig.MaxUserReactionsPerBatch, nameof(args.Limit));
Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId));
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);


int limit = args.Limit.GetValueOrDefault(int.MaxValue);
int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxUserReactionsPerBatch);
ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); ulong afterUserId = args.AfterUserId.GetValueOrDefault(0);


var ids = new BucketIds(channelId: channelId); var ids = new BucketIds(channelId: channelId);
Expression<Func<string>> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}";
Expression<Func<string>> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}?limit={limit}&after={afterUserId}";
return await SendAsync<IReadOnlyCollection<User>>("GET", endpoint, ids, options: options).ConfigureAwait(false); return await SendAsync<IReadOnlyCollection<User>>("GET", endpoint, ids, options: options).ConfigureAwait(false);
} }
public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
@@ -800,6 +800,15 @@ namespace Discord.API
var ids = new BucketIds(guildId: guildId); var ids = new BucketIds(guildId: guildId);
return await SendAsync<IReadOnlyCollection<Ban>>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false); return await SendAsync<IReadOnlyCollection<Ban>>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false);
} }
public async Task<Ban> GetGuildBanAsync(ulong guildId, ulong userId, RequestOptions options)
{
Preconditions.NotEqual(userId, 0, nameof(userId));
Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
return await SendAsync<Ban>("GET", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false);
}
public async Task CreateGuildBanAsync(ulong guildId, ulong userId, CreateGuildBanParams args, RequestOptions options = null) public async Task CreateGuildBanAsync(ulong guildId, ulong userId, CreateGuildBanParams args, RequestOptions options = null)
{ {
Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(guildId, 0, nameof(guildId));
@@ -897,7 +906,7 @@ namespace Discord.API
} }


//Guild Invites //Guild Invites
public async Task<Invite> GetInviteAsync(string inviteId, RequestOptions options = null)
public async Task<InviteMetadata> GetInviteAsync(string inviteId, GetInviteParams args, RequestOptions options = null)
{ {
Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId));
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
@@ -910,9 +919,11 @@ namespace Discord.API
if (index >= 0) if (index >= 0)
inviteId = inviteId.Substring(index + 1); inviteId = inviteId.Substring(index + 1);


var withCounts = args.WithCounts.GetValueOrDefault(false);

try try
{ {
return await SendAsync<Invite>("GET", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false);
return await SendAsync<InviteMetadata>("GET", () => $"invites/{inviteId}?with_counts={withCounts}", new BucketIds(), options: options).ConfigureAwait(false);
} }
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; }
} }
@@ -950,13 +961,6 @@ namespace Discord.API
return await SendAsync<Invite>("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); return await SendAsync<Invite>("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false);
} }
public async Task AcceptInviteAsync(string inviteId, RequestOptions options = null)
{
Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId));
options = RequestOptions.CreateOrClone(options);
await SendAsync("POST", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false);
}


//Guild Members //Guild Members
public async Task<GuildMember> GetGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null) public async Task<GuildMember> GetGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null)
@@ -1202,6 +1206,26 @@ namespace Discord.API
return await SendAsync<IReadOnlyCollection<VoiceRegion>>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false); return await SendAsync<IReadOnlyCollection<VoiceRegion>>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false);
} }


//Audit logs
public async Task<AuditLog> GetAuditLogsAsync(ulong guildId, GetAuditLogsParams args, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotNull(args, nameof(args));
options = RequestOptions.CreateOrClone(options);

int limit = args.Limit.GetValueOrDefault(int.MaxValue);

var ids = new BucketIds(guildId: guildId);
Expression<Func<string>> endpoint;

if (args.BeforeEntryId.IsSpecified)
endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}&before={args.BeforeEntryId.Value}";
else
endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}";

return await SendAsync<AuditLog>("GET", endpoint, ids, options: options).ConfigureAwait(false);
}

//Webhooks //Webhooks
public async Task<Webhook> CreateWebhookAsync(ulong channelId, CreateWebhookParams args, RequestOptions options = null) public async Task<Webhook> CreateWebhookAsync(ulong channelId, CreateWebhookParams args, RequestOptions options = null)
{ {


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

@@ -56,8 +56,8 @@ namespace Discord.Rest
=> ClientHelper.GetConnectionsAsync(this, options); => ClientHelper.GetConnectionsAsync(this, options);


/// <inheritdoc /> /// <inheritdoc />
public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null)
=> ClientHelper.GetInviteAsync(this, inviteId, options);
public Task<RestInviteMetadata> GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null)
=> ClientHelper.GetInviteAsync(this, inviteId, withCount, options);


/// <inheritdoc /> /// <inheritdoc />
public Task<RestGuild> GetGuildAsync(ulong id, RequestOptions options = null) public Task<RestGuild> GetGuildAsync(ulong id, RequestOptions options = null)
@@ -131,8 +131,8 @@ namespace Discord.Rest
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options)
=> await GetConnectionsAsync(options).ConfigureAwait(false); => await GetConnectionsAsync(options).ConfigureAwait(false);


async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options)
=> await GetInviteAsync(inviteId, options).ConfigureAwait(false);
async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options)
=> await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false);


async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options)
{ {


+ 58
- 0
src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs View File

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

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
internal static class AuditLogHelper
{
private static readonly Dictionary<ActionType, Func<BaseDiscordClient, Model, EntryModel, IAuditLogData>> CreateMapping
= new Dictionary<ActionType, Func<BaseDiscordClient, Model, EntryModel, IAuditLogData>>()
{
[ActionType.GuildUpdated] = GuildUpdateAuditLogData.Create,

[ActionType.ChannelCreated] = ChannelCreateAuditLogData.Create,
[ActionType.ChannelUpdated] = ChannelUpdateAuditLogData.Create,
[ActionType.ChannelDeleted] = ChannelDeleteAuditLogData.Create,

[ActionType.OverwriteCreated] = OverwriteCreateAuditLogData.Create,
[ActionType.OverwriteUpdated] = OverwriteUpdateAuditLogData.Create,
[ActionType.OverwriteDeleted] = OverwriteDeleteAuditLogData.Create,

[ActionType.Kick] = KickAuditLogData.Create,
[ActionType.Prune] = PruneAuditLogData.Create,
[ActionType.Ban] = BanAuditLogData.Create,
[ActionType.Unban] = UnbanAuditLogData.Create,
[ActionType.MemberUpdated] = MemberUpdateAuditLogData.Create,
[ActionType.MemberRoleUpdated] = MemberRoleAuditLogData.Create,

[ActionType.RoleCreated] = RoleCreateAuditLogData.Create,
[ActionType.RoleUpdated] = RoleUpdateAuditLogData.Create,
[ActionType.RoleDeleted] = RoleDeleteAuditLogData.Create,

[ActionType.InviteCreated] = InviteCreateAuditLogData.Create,
[ActionType.InviteUpdated] = InviteUpdateAuditLogData.Create,
[ActionType.InviteDeleted] = InviteDeleteAuditLogData.Create,

[ActionType.WebhookCreated] = WebhookCreateAuditLogData.Create,
[ActionType.WebhookUpdated] = WebhookUpdateAuditLogData.Create,
[ActionType.WebhookDeleted] = WebhookDeleteAuditLogData.Create,

[ActionType.EmojiCreated] = EmoteCreateAuditLogData.Create,
[ActionType.EmojiUpdated] = EmoteUpdateAuditLogData.Create,
[ActionType.EmojiDeleted] = EmoteDeleteAuditLogData.Create,

[ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create,
};

public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry)
{
if (CreateMapping.TryGetValue(entry.Action, out var func))
return func(discord, log, entry);

return null;
}
}
}

+ 23
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs View File

@@ -0,0 +1,23 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class BanAuditLogData : IAuditLogData
{
private BanAuditLogData(IUser user)
{
Target = user;
}

internal static BanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
return new BanAuditLogData(RestUser.Create(discord, userInfo));
}

public IUser Target { get; }
}
}

+ 52
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs View File

@@ -0,0 +1,52 @@
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class ChannelCreateAuditLogData : IAuditLogData
{
private ChannelCreateAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection<Overwrite> overwrites)
{
ChannelId = id;
ChannelName = name;
ChannelType = type;
Overwrites = overwrites;
}

internal static ChannelCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;
var overwrites = new List<Overwrite>();

var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites");
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type");
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");

var type = typeModel.NewValue.ToObject<ChannelType>();
var name = nameModel.NewValue.ToObject<string>();

foreach (var overwrite in overwritesModel.NewValue)
{
var deny = overwrite.Value<ulong>("deny");
var _type = overwrite.Value<string>("type");
var id = overwrite.Value<ulong>("id");
var allow = overwrite.Value<ulong>("allow");

PermissionTarget permType = _type == "member" ? PermissionTarget.User : PermissionTarget.Role;

overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny)));
}

return new ChannelCreateAuditLogData(entry.TargetId.Value, name, type, overwrites.ToReadOnlyCollection());
}

public ulong ChannelId { get; }
public string ChannelName { get; }
public ChannelType ChannelType { get; }
public IReadOnlyCollection<Overwrite> Overwrites { get; }
}
}

+ 45
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs View File

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

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class ChannelDeleteAuditLogData : IAuditLogData
{
private ChannelDeleteAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection<Overwrite> overwrites)
{
ChannelId = id;
ChannelName = name;
ChannelType = type;
Overwrites = overwrites;
}

internal static ChannelDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites");
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type");
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");

var overwrites = overwritesModel.OldValue.ToObject<API.Overwrite[]>()
.Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny)))
.ToList();
var type = typeModel.OldValue.ToObject<ChannelType>();
var name = nameModel.OldValue.ToObject<string>();
var id = entry.TargetId.Value;

return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection());
}

public ulong ChannelId { get; }
public string ChannelName { get; }
public ChannelType ChannelType { get; }
public IReadOnlyCollection<Overwrite> Overwrites { get; }
}
}

+ 18
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs View File

@@ -0,0 +1,18 @@
namespace Discord.Rest
{
public struct ChannelInfo
{
internal ChannelInfo(string name, string topic, int? bitrate, int? limit)
{
Name = name;
Topic = topic;
Bitrate = bitrate;
UserLimit = limit;
}

public string Name { get; }
public string Topic { get; }
public int? Bitrate { get; }
public int? UserLimit { get; }
}
}

+ 45
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs View File

@@ -0,0 +1,45 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class ChannelUpdateAuditLogData : IAuditLogData
{
private ChannelUpdateAuditLogData(ulong id, ChannelInfo before, ChannelInfo after)
{
ChannelId = id;
Before = before;
After = after;
}

internal static ChannelUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");
var topicModel = changes.FirstOrDefault(x => x.ChangedProperty == "topic");
var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate");
var userLimitModel = changes.FirstOrDefault(x => x.ChangedProperty == "user_limit");

string oldName = nameModel?.OldValue?.ToObject<string>(),
newName = nameModel?.NewValue?.ToObject<string>();
string oldTopic = topicModel?.OldValue?.ToObject<string>(),
newTopic = topicModel?.NewValue?.ToObject<string>();
int? oldBitrate = bitrateModel?.OldValue?.ToObject<int>(),
newBitrate = bitrateModel?.NewValue?.ToObject<int>();
int? oldLimit = userLimitModel?.OldValue?.ToObject<int>(),
newLimit = userLimitModel?.NewValue?.ToObject<int>();

var before = new ChannelInfo(oldName, oldTopic, oldBitrate, oldLimit);
var after = new ChannelInfo(newName, newTopic, newBitrate, newLimit);

return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after);
}

public ulong ChannelId { get; }
public ChannelInfo Before { get; set; }
public ChannelInfo After { get; set; }
}
}

+ 31
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs View File

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

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class EmoteCreateAuditLogData : IAuditLogData
{
private EmoteCreateAuditLogData(ulong id, string name)
{
EmoteId = id;
Name = name;
}

internal static EmoteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name");

var emoteName = change.NewValue?.ToObject<string>();
return new EmoteCreateAuditLogData(entry.TargetId.Value, emoteName);
}

public ulong EmoteId { get; }
public string Name { get; }
}
}

+ 28
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs View File

@@ -0,0 +1,28 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class EmoteDeleteAuditLogData : IAuditLogData
{
private EmoteDeleteAuditLogData(ulong id, string name)
{
EmoteId = id;
Name = name;
}

internal static EmoteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name");

var emoteName = change.OldValue?.ToObject<string>();

return new EmoteDeleteAuditLogData(entry.TargetId.Value, emoteName);
}

public ulong EmoteId { get; }
public string Name { get; }
}
}

+ 31
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs View File

@@ -0,0 +1,31 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class EmoteUpdateAuditLogData : IAuditLogData
{
private EmoteUpdateAuditLogData(ulong id, string oldName, string newName)
{
EmoteId = id;
OldName = oldName;
NewName = newName;
}

internal static EmoteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name");

var newName = change.NewValue?.ToObject<string>();
var oldName = change.OldValue?.ToObject<string>();

return new EmoteUpdateAuditLogData(entry.TargetId.Value, oldName, newName);
}

public ulong EmoteId { get; }
public string NewName { get; }
public string OldName { get; }
}
}

+ 32
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs View File

@@ -0,0 +1,32 @@
namespace Discord.Rest
{
public struct GuildInfo
{
internal GuildInfo(int? afkTimeout, DefaultMessageNotifications? defaultNotifs,
ulong? afkChannel, string name, string region, string icon,
VerificationLevel? verification, IUser owner, MfaLevel? mfa, int? filter)
{
AfkTimeout = afkTimeout;
DefaultMessageNotifications = defaultNotifs;
AfkChannelId = afkChannel;
Name = name;
RegionId = region;
IconHash = icon;
VerificationLevel = verification;
Owner = owner;
MfaLevel = mfa;
ContentFilterLevel = filter;
}

public int? AfkTimeout { get; }
public DefaultMessageNotifications? DefaultMessageNotifications { get; }
public ulong? AfkChannelId { get; }
public string Name { get; }
public string RegionId { get; }
public string IconHash { get; }
public VerificationLevel? VerificationLevel { get; }
public IUser Owner { get; }
public MfaLevel? MfaLevel { get; }
public int? ContentFilterLevel { get; }
}
}

+ 79
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs View File

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

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class GuildUpdateAuditLogData : IAuditLogData
{
private GuildUpdateAuditLogData(GuildInfo before, GuildInfo after)
{
Before = before;
After = after;
}

internal static GuildUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var afkTimeoutModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var defaultMessageNotificationsModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var afkChannelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var regionIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var iconHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var verificationLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var ownerIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var mfaLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");
var contentFilterModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout");

int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject<int>(),
newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject<int>();
DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject<DefaultMessageNotifications>(),
newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject<DefaultMessageNotifications>();
ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject<ulong>(),
newAfkChannelId = afkChannelModel?.NewValue?.ToObject<ulong>();
string oldName = nameModel?.OldValue?.ToObject<string>(),
newName = nameModel?.NewValue?.ToObject<string>();
string oldRegionId = regionIdModel?.OldValue?.ToObject<string>(),
newRegionId = regionIdModel?.NewValue?.ToObject<string>();
string oldIconHash = iconHashModel?.OldValue?.ToObject<string>(),
newIconHash = iconHashModel?.NewValue?.ToObject<string>();
VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject<VerificationLevel>(),
newVerificationLevel = verificationLevelModel?.NewValue?.ToObject<VerificationLevel>();
ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject<ulong>(),
newOwnerId = ownerIdModel?.NewValue?.ToObject<ulong>();
MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject<MfaLevel>(),
newMfaLevel = mfaLevelModel?.NewValue?.ToObject<MfaLevel>();
int? oldContentFilter = contentFilterModel?.OldValue?.ToObject<int>(),
newContentFilter = contentFilterModel?.NewValue?.ToObject<int>();

IUser oldOwner = null;
if (oldOwnerId != null)
{
var oldOwnerInfo = log.Users.FirstOrDefault(x => x.Id == oldOwnerId.Value);
oldOwner = RestUser.Create(discord, oldOwnerInfo);
}

IUser newOwner = null;
if (newOwnerId != null)
{
var newOwnerInfo = log.Users.FirstOrDefault(x => x.Id == newOwnerId.Value);
newOwner = RestUser.Create(discord, newOwnerInfo);
}

var before = new GuildInfo(oldAfkTimeout, oldDefaultMessageNotifications,
oldAfkChannelId, oldName, oldRegionId, oldIconHash, oldVerificationLevel, oldOwner,
oldMfaLevel, oldContentFilter);
var after = new GuildInfo(newAfkTimeout, newDefaultMessageNotifications,
newAfkChannelId, newName, newRegionId, newIconHash, newVerificationLevel, newOwner,
newMfaLevel, newContentFilter);

return new GuildUpdateAuditLogData(before, after);
}

public GuildInfo Before { get; }
public GuildInfo After { get; }
}
}

+ 55
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs View File

@@ -0,0 +1,55 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class InviteCreateAuditLogData : IAuditLogData
{
private InviteCreateAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses)
{
MaxAge = maxAge;
Code = code;
Temporary = temporary;
Creator = inviter;
ChannelId = channelId;
Uses = uses;
MaxUses = maxUses;
}

internal static InviteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age");
var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code");
var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary");
var inviterIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "inviter_id");
var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id");
var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses");
var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses");

var maxAge = maxAgeModel.NewValue.ToObject<int>();
var code = codeModel.NewValue.ToObject<string>();
var temporary = temporaryModel.NewValue.ToObject<bool>();
var inviterId = inviterIdModel.NewValue.ToObject<ulong>();
var channelId = channelIdModel.NewValue.ToObject<ulong>();
var uses = usesModel.NewValue.ToObject<int>();
var maxUses = maxUsesModel.NewValue.ToObject<int>();

var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId);
var inviter = RestUser.Create(discord, inviterInfo);

return new InviteCreateAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses);
}

public int MaxAge { get; }
public string Code { get; }
public bool Temporary { get; }
public IUser Creator { get; }
public ulong ChannelId { get; }
public int Uses { get; }
public int MaxUses { get; }
}
}

+ 55
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs View File

@@ -0,0 +1,55 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class InviteDeleteAuditLogData : IAuditLogData
{
private InviteDeleteAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses)
{
MaxAge = maxAge;
Code = code;
Temporary = temporary;
Creator = inviter;
ChannelId = channelId;
Uses = uses;
MaxUses = maxUses;
}

internal static InviteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age");
var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code");
var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary");
var inviterIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "inviter_id");
var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id");
var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses");
var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses");

var maxAge = maxAgeModel.OldValue.ToObject<int>();
var code = codeModel.OldValue.ToObject<string>();
var temporary = temporaryModel.OldValue.ToObject<bool>();
var inviterId = inviterIdModel.OldValue.ToObject<ulong>();
var channelId = channelIdModel.OldValue.ToObject<ulong>();
var uses = usesModel.OldValue.ToObject<int>();
var maxUses = maxUsesModel.OldValue.ToObject<int>();

var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId);
var inviter = RestUser.Create(discord, inviterInfo);

return new InviteDeleteAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses);
}

public int MaxAge { get; }
public string Code { get; }
public bool Temporary { get; }
public IUser Creator { get; }
public ulong ChannelId { get; }
public int Uses { get; }
public int MaxUses { get; }
}
}

+ 20
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs View File

@@ -0,0 +1,20 @@
namespace Discord.Rest
{
public struct InviteInfo
{
internal InviteInfo(int? maxAge, string code, bool? temporary, ulong? channelId, int? maxUses)
{
MaxAge = maxAge;
Code = code;
Temporary = temporary;
ChannelId = channelId;
MaxUses = maxUses;
}

public int? MaxAge { get; }
public string Code { get; }
public bool? Temporary { get; }
public ulong? ChannelId { get; }
public int? MaxUses { get; }
}
}

+ 46
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs View File

@@ -0,0 +1,46 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class InviteUpdateAuditLogData : IAuditLogData
{
private InviteUpdateAuditLogData(InviteInfo before, InviteInfo after)
{
Before = before;
After = after;
}

internal static InviteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age");
var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code");
var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary");
var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id");
var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses");

int? oldMaxAge = maxAgeModel?.OldValue?.ToObject<int>(),
newMaxAge = maxAgeModel?.NewValue?.ToObject<int>();
string oldCode = codeModel?.OldValue?.ToObject<string>(),
newCode = codeModel?.NewValue?.ToObject<string>();
bool? oldTemporary = temporaryModel?.OldValue?.ToObject<bool>(),
newTemporary = temporaryModel?.NewValue?.ToObject<bool>();
ulong? oldChannelId = channelIdModel?.OldValue?.ToObject<ulong>(),
newChannelId = channelIdModel?.NewValue?.ToObject<ulong>();
int? oldMaxUses = maxUsesModel?.OldValue?.ToObject<int>(),
newMaxUses = maxUsesModel?.NewValue?.ToObject<int>();

var before = new InviteInfo(oldMaxAge, oldCode, oldTemporary, oldChannelId, oldMaxUses);
var after = new InviteInfo(newMaxAge, newCode, newTemporary, newChannelId, newMaxUses);

return new InviteUpdateAuditLogData(before, after);
}

public InviteInfo Before { get; }
public InviteInfo After { get; }
}
}

+ 23
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs View File

@@ -0,0 +1,23 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class KickAuditLogData : IAuditLogData
{
private KickAuditLogData(RestUser user)
{
Target = user;
}

internal static KickAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
return new KickAuditLogData(RestUser.Create(discord, userInfo));
}

public IUser Target { get; }
}
}

+ 50
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs View File

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

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class MemberRoleAuditLogData : IAuditLogData
{
private MemberRoleAuditLogData(IReadOnlyCollection<RoleInfo> roles, IUser target)
{
Roles = roles;
Target = target;
}

internal static MemberRoleAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var roleInfos = changes.SelectMany(x => x.NewValue.ToObject<API.Role[]>(),
(model, role) => new { model.ChangedProperty, Role = role })
.Select(x => new RoleInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add"))
.ToList();

var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
var user = RestUser.Create(discord, userInfo);

return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user);
}

public IReadOnlyCollection<RoleInfo> Roles { get; }
public IUser Target { get; }

public struct RoleInfo
{
internal RoleInfo(string name, ulong roleId, bool added)
{
Name = name;
RoleId = roleId;
Added = added;
}

public string Name { get; }
public ulong RoleId { get; }
public bool Added { get; }
}
}
}

+ 35
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs View File

@@ -0,0 +1,35 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;
using ChangeModel = Discord.API.AuditLogChange;

namespace Discord.Rest
{
public class MemberUpdateAuditLogData : IAuditLogData
{
private MemberUpdateAuditLogData(IUser target, string newNick, string oldNick)
{
Target = target;
NewNick = newNick;
OldNick = oldNick;
}

internal static MemberUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "nick");

var newNick = changes.NewValue?.ToObject<string>();
var oldNick = changes.OldValue?.ToObject<string>();

var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
var user = RestUser.Create(discord, targetInfo);

return new MemberUpdateAuditLogData(user, newNick, oldNick);
}

public IUser Target { get; }
public string NewNick { get; }
public string OldNick { get; }
}
}

+ 22
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs View File

@@ -0,0 +1,22 @@
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class MessageDeleteAuditLogData : IAuditLogData
{
private MessageDeleteAuditLogData(ulong channelId, int count)
{
ChannelId = channelId;
MessageCount = count;
}

internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value);
}

public int MessageCount { get; }
public ulong ChannelId { get; }
}
}

+ 37
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs View File

@@ -0,0 +1,37 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class OverwriteCreateAuditLogData : IAuditLogData
{
private OverwriteCreateAuditLogData(Overwrite overwrite)
{
Overwrite = overwrite;
}

internal static OverwriteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny");
var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow");

var deny = denyModel.NewValue.ToObject<ulong>();
var allow = allowModel.NewValue.ToObject<ulong>();

var permissions = new OverwritePermissions(allow, deny);

var id = entry.Options.OverwriteTargetId.Value;
var type = entry.Options.OverwriteType;

PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role;

return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions));
}

public Overwrite Overwrite { get; }
}
}

+ 42
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs View File

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

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;
using ChangeModel = Discord.API.AuditLogChange;
using OptionModel = Discord.API.AuditLogOptions;

namespace Discord.Rest
{
public class OverwriteDeleteAuditLogData : IAuditLogData
{
private OverwriteDeleteAuditLogData(Overwrite deletedOverwrite)
{
Overwrite = deletedOverwrite;
}

internal static OverwriteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny");
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type");
var idModel = changes.FirstOrDefault(x => x.ChangedProperty == "id");
var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow");

var deny = denyModel.OldValue.ToObject<ulong>();
var type = typeModel.OldValue.ToObject<string>();
var id = idModel.OldValue.ToObject<ulong>();
var allow = allowModel.OldValue.ToObject<ulong>();

PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role;

return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny)));
}

public Overwrite Overwrite { get; }
}
}

+ 44
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs View File

@@ -0,0 +1,44 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class OverwriteUpdateAuditLogData : IAuditLogData
{
private OverwriteUpdateAuditLogData(OverwritePermissions before, OverwritePermissions after, ulong targetId, PermissionTarget targetType)
{
OldPermissions = before;
NewPermissions = after;
OverwriteTargetId = targetId;
OverwriteType = targetType;
}

internal static OverwriteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny");
var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow");

var beforeAllow = allowModel?.OldValue?.ToObject<ulong>();
var afterAllow = allowModel?.NewValue?.ToObject<ulong>();
var beforeDeny = denyModel?.OldValue?.ToObject<ulong>();
var afterDeny = denyModel?.OldValue?.ToObject<ulong>();

var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0);
var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0);

PermissionTarget target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role;

return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target);
}

public OverwritePermissions OldPermissions { get; }
public OverwritePermissions NewPermissions { get; }

public ulong OverwriteTargetId { get; }
public PermissionTarget OverwriteType { get; }
}
}

+ 22
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs View File

@@ -0,0 +1,22 @@
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class PruneAuditLogData : IAuditLogData
{
private PruneAuditLogData(int pruneDays, int membersRemoved)
{
PruneDays = pruneDays;
MembersRemoved = membersRemoved;
}

internal static PruneAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
return new PruneAuditLogData(entry.Options.PruneDeleteMemberDays.Value, entry.Options.PruneMembersRemoved.Value);
}

public int PruneDays { get; }
public int MembersRemoved { get; }
}
}

+ 47
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs View File

@@ -0,0 +1,47 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class RoleCreateAuditLogData : IAuditLogData
{
private RoleCreateAuditLogData(ulong id, RoleInfo props)
{
RoleId = id;
Properties = props;
}

internal static RoleCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color");
var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable");
var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist");
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");
var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions");

uint? colorRaw = colorModel?.NewValue?.ToObject<uint>();
bool? mentionable = mentionableModel?.NewValue?.ToObject<bool>();
bool? hoist = hoistModel?.NewValue?.ToObject<bool>();
string name = nameModel?.NewValue?.ToObject<string>();
ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject<ulong>();

Color? color = null;
GuildPermissions? permissions = null;

if (colorRaw.HasValue)
color = new Color(colorRaw.Value);
if (permissionsRaw.HasValue)
permissions = new GuildPermissions(permissionsRaw.Value);

return new RoleCreateAuditLogData(entry.TargetId.Value,
new RoleInfo(color, mentionable, hoist, name, permissions));
}

public ulong RoleId { get; }
public RoleInfo Properties { get; }
}
}

+ 47
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs View File

@@ -0,0 +1,47 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class RoleDeleteAuditLogData : IAuditLogData
{
private RoleDeleteAuditLogData(ulong id, RoleInfo props)
{
RoleId = id;
Properties = props;
}

internal static RoleDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color");
var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable");
var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist");
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");
var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions");

uint? colorRaw = colorModel?.OldValue?.ToObject<uint>();
bool? mentionable = mentionableModel?.OldValue?.ToObject<bool>();
bool? hoist = hoistModel?.OldValue?.ToObject<bool>();
string name = nameModel?.OldValue?.ToObject<string>();
ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>();

Color? color = null;
GuildPermissions? permissions = null;

if (colorRaw.HasValue)
color = new Color(colorRaw.Value);
if (permissionsRaw.HasValue)
permissions = new GuildPermissions(permissionsRaw.Value);

return new RoleDeleteAuditLogData(entry.TargetId.Value,
new RoleInfo(color, mentionable, hoist, name, permissions));
}

public ulong RoleId { get; }
public RoleInfo Properties { get; }
}
}

+ 21
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs View File

@@ -0,0 +1,21 @@
namespace Discord.Rest
{
public struct RoleInfo
{
internal RoleInfo(Color? color, bool? mentionable, bool? hoist, string name,
GuildPermissions? permissions)
{
Color = color;
Mentionable = mentionable;
Hoist = hoist;
Name = name;
Permissions = permissions;
}

public Color? Color { get; }
public bool? Mentionable { get; }
public bool? Hoist { get; }
public string Name { get; }
public GuildPermissions? Permissions { get; }
}
}

+ 62
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs View File

@@ -0,0 +1,62 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class RoleUpdateAuditLogData : IAuditLogData
{
private RoleUpdateAuditLogData(ulong id, RoleInfo oldProps, RoleInfo newProps)
{
RoleId = id;
Before = oldProps;
After = newProps;
}

internal static RoleUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color");
var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable");
var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist");
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");
var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions");

uint? oldColorRaw = colorModel?.OldValue?.ToObject<uint>(),
newColorRaw = colorModel?.NewValue?.ToObject<uint>();
bool? oldMentionable = mentionableModel?.OldValue?.ToObject<bool>(),
newMentionable = mentionableModel?.NewValue?.ToObject<bool>();
bool? oldHoist = hoistModel?.OldValue?.ToObject<bool>(),
newHoist = hoistModel?.NewValue?.ToObject<bool>();
string oldName = nameModel?.OldValue?.ToObject<string>(),
newName = nameModel?.NewValue?.ToObject<string>();
ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>(),
newPermissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>();

Color? oldColor = null,
newColor = null;
GuildPermissions? oldPermissions = null,
newPermissions = null;

if (oldColorRaw.HasValue)
oldColor = new Color(oldColorRaw.Value);
if (newColorRaw.HasValue)
newColor = new Color(newColorRaw.Value);
if (oldPermissionsRaw.HasValue)
oldPermissions = new GuildPermissions(oldPermissionsRaw.Value);
if (newPermissionsRaw.HasValue)
newPermissions = new GuildPermissions(newPermissionsRaw.Value);

var oldProps = new RoleInfo(oldColor, oldMentionable, oldHoist, oldName, oldPermissions);
var newProps = new RoleInfo(newColor, newMentionable, newHoist, newName, newPermissions);

return new RoleUpdateAuditLogData(entry.TargetId.Value, oldProps, newProps);
}

public ulong RoleId { get; }
public RoleInfo Before { get; }
public RoleInfo After { get; }
}
}

+ 23
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs View File

@@ -0,0 +1,23 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class UnbanAuditLogData : IAuditLogData
{
private UnbanAuditLogData(IUser user)
{
Target = user;
}

internal static UnbanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
return new UnbanAuditLogData(RestUser.Create(discord, userInfo));
}

public IUser Target { get; }
}
}

+ 44
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs View File

@@ -0,0 +1,44 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class WebhookCreateAuditLogData : IAuditLogData
{
private WebhookCreateAuditLogData(IWebhook webhook, WebhookType type, string name, ulong channelId)
{
Webhook = webhook;
Name = name;
Type = type;
ChannelId = channelId;
}

internal static WebhookCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id");
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type");
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");

var channelId = channelIdModel.NewValue.ToObject<ulong>();
var type = typeModel.NewValue.ToObject<WebhookType>();
var name = nameModel.NewValue.ToObject<string>();

var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId);
var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo);

return new WebhookCreateAuditLogData(webhook, type, name, channelId);
}

//Corresponds to the *current* data
public IWebhook Webhook { get; }

//Corresponds to the *audit log* data
public WebhookType Type { get; }
public string Name { get; }
public ulong ChannelId { get; }
}
}

+ 46
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs View File

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

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class WebhookDeleteAuditLogData : IAuditLogData
{
private WebhookDeleteAuditLogData(ulong id, ulong channel, WebhookType type, string name, string avatar)
{
WebhookId = id;
ChannelId = channel;
Name = name;
Type = type;
Avatar = avatar;
}

internal static WebhookDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id");
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type");
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");
var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash");

var channelId = channelIdModel.OldValue.ToObject<ulong>();
var type = typeModel.OldValue.ToObject<WebhookType>();
var name = nameModel.OldValue.ToObject<string>();
var avatarHash = avatarHashModel?.OldValue?.ToObject<string>();

return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, type, name, avatarHash);
}

public ulong WebhookId { get; }
public ulong ChannelId { get; }
public WebhookType Type { get; }
public string Name { get; }
public string Avatar { get; }
}
}

+ 16
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs View File

@@ -0,0 +1,16 @@
namespace Discord.Rest
{
public struct WebhookInfo
{
internal WebhookInfo(string name, ulong? channelId, string avatar)
{
Name = name;
ChannelId = channelId;
Avatar = avatar;
}

public string Name { get; }
public ulong? ChannelId { get; }
public string Avatar { get; }
}
}

+ 52
- 0
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs View File

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

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class WebhookUpdateAuditLogData : IAuditLogData
{
private WebhookUpdateAuditLogData(IWebhook webhook, WebhookInfo before, WebhookInfo after)
{
Webhook = webhook;
Before = before;
After = after;
}

internal static WebhookUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var changes = entry.Changes;

var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name");
var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id");
var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash");

var oldName = nameModel?.OldValue?.ToObject<string>();
var oldChannelId = channelIdModel?.OldValue?.ToObject<ulong>();
var oldAvatar = avatarHashModel?.OldValue?.ToObject<string>();
var before = new WebhookInfo(oldName, oldChannelId, oldAvatar);

var newName = nameModel?.NewValue?.ToObject<string>();
var newChannelId = channelIdModel?.NewValue?.ToObject<ulong>();
var newAvatar = avatarHashModel?.NewValue?.ToObject<string>();
var after = new WebhookInfo(newName, newChannelId, newAvatar);

var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId);
var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo);

return new WebhookUpdateAuditLogData(webhook, before, after);
}

//Again, the *current* data
public IWebhook Webhook { get; }

//And the *audit log* data
public WebhookInfo Before { get; }
public WebhookInfo After { get; }
}
}

+ 38
- 0
src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs View File

@@ -0,0 +1,38 @@
using System.Linq;

using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;

namespace Discord.Rest
{
public class RestAuditLogEntry : RestEntity<ulong>, IAuditLogEntry
{
private RestAuditLogEntry(BaseDiscordClient discord, Model fullLog, EntryModel model, IUser user)
: base(discord, model.Id)
{
Action = model.Action;
Data = AuditLogHelper.CreateData(discord, fullLog, model);
User = user;
Reason = model.Reason;
}

internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model)
{
var userInfo = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId);
IUser user = null;
if (userInfo != null)
user = RestUser.Create(discord, userInfo);

return new RestAuditLogEntry(discord, fullLog, model, user);
}

/// <inheritdoc/>
public ActionType Action { get; }
/// <inheritdoc/>
public IAuditLogData Data { get; }
/// <inheritdoc/>
public IUser User { get; }
/// <inheritdoc/>
public string Reason { get; }
}
}

+ 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, Embed embed = null, RequestOptions options = null);
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null);
#if FILESYSTEM #if FILESYSTEM
/// <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, Embed embed = null, RequestOptions options = null); new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null);


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

@@ -63,7 +63,7 @@ namespace Discord.Rest
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
#if FILESYSTEM #if FILESYSTEM
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)


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

@@ -81,7 +81,7 @@ namespace Discord.Rest
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
#if FILESYSTEM #if FILESYSTEM
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)


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

@@ -58,7 +58,7 @@ namespace Discord.Rest
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


public Task<RestUserMessage> SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
#if FILESYSTEM #if FILESYSTEM
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)


+ 3
- 3
src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs View File

@@ -33,13 +33,13 @@ namespace Discord.Rest
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);


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


public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)


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

@@ -1,4 +1,4 @@
using Discord.API.Rest;
using Discord.API.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -111,6 +111,11 @@ namespace Discord.Rest
var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options).ConfigureAwait(false); var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options).ConfigureAwait(false);
return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray();
} }
public static async Task<RestBan> GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options)
{
var model = await client.ApiClient.GetGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false);
return RestBan.Create(client, model);
}


public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client, public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client,
ulong userId, int pruneDays, string reason, RequestOptions options) ulong userId, int pruneDays, string reason, RequestOptions options)
@@ -140,20 +145,36 @@ namespace Discord.Rest
return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray(); return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray();
} }
public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, BaseDiscordClient client,
string name, RequestOptions options)
string name, RequestOptions options, Action<TextChannelProperties> func = null)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));


var args = new CreateGuildChannelParams(name, ChannelType.Text);
var props = new TextChannelProperties();
func?.Invoke(props);

var args = new CreateGuildChannelParams(name, ChannelType.Text)
{
CategoryId = props.CategoryId,
Topic = props.Topic,
IsNsfw = props.IsNsfw
};
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false);
return RestTextChannel.Create(client, guild, model); return RestTextChannel.Create(client, guild, model);
} }
public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client,
string name, RequestOptions options)
string name, RequestOptions options, Action<VoiceChannelProperties> func = null)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));


var args = new CreateGuildChannelParams(name, ChannelType.Voice);
var props = new VoiceChannelProperties();
func?.Invoke(props);

var args = new CreateGuildChannelParams(name, ChannelType.Voice)
{
CategoryId = props.CategoryId,
Bitrate = props.Bitrate,
UserLimit = props.UserLimit
};
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false);
return RestVoiceChannel.Create(client, guild, model); return RestVoiceChannel.Create(client, guild, model);
} }
@@ -263,6 +284,35 @@ namespace Discord.Rest
return model.Pruned; return model.Pruned;
} }


// Audit logs
public static IAsyncEnumerable<IReadOnlyCollection<RestAuditLogEntry>> GetAuditLogsAsync(IGuild guild, BaseDiscordClient client,
ulong? from, int? limit, RequestOptions options)
{
return new PagedAsyncEnumerable<RestAuditLogEntry>(
DiscordConfig.MaxAuditLogEntriesPerBatch,
async (info, ct) =>
{
var args = new GetAuditLogsParams
{
Limit = info.PageSize
};
if (info.Position != null)
args.BeforeEntryId = info.Position.Value;
var model = await client.ApiClient.GetAuditLogsAsync(guild.Id, args, options);
return model.Entries.Select((x) => RestAuditLogEntry.Create(client, model, x)).ToImmutableArray();
},
nextPage: (info, lastPage) =>
{
if (lastPage.Count != DiscordConfig.MaxAuditLogEntriesPerBatch)
return false;
info.Position = lastPage.Min(x => x.Id);
return true;
},
start: from,
count: limit
);
}

//Webhooks //Webhooks
public static async Task<RestWebhook> GetWebhookAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) public static async Task<RestWebhook> GetWebhookAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options)
{ {


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

Loading…
Cancel
Save