Browse Source

Merge remote-tracking branch 'upstream/dev' into dev

pull/1639/head
Fyers 4 years ago
parent
commit
76f7149288
100 changed files with 1441 additions and 236 deletions
  1. +2
    -3
      .gitignore
  2. +3
    -3
      Discord.Net.targets
  3. +1
    -1
      README.md
  4. +9
    -0
      StyleAnalyzer.targets
  5. BIN
      docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll
  6. BIN
      docs/_template/last-modified/plugins/LibGit2Sharp.dll
  7. +3
    -3
      docs/_template/last-modified/plugins/LibGit2Sharp.dll.config
  8. BIN
      docs/_template/last-modified/plugins/lib/alpine-x64/libgit2-ef5a385.so
  9. BIN
      docs/_template/last-modified/plugins/lib/alpine.3.9-x64/libgit2-ef5a385.so
  10. BIN
      docs/_template/last-modified/plugins/lib/debian-arm64/libgit2-ef5a385.so
  11. BIN
      docs/_template/last-modified/plugins/lib/debian.9-x64/libgit2-ef5a385.so
  12. BIN
      docs/_template/last-modified/plugins/lib/fedora-x64/libgit2-ef5a385.so
  13. BIN
      docs/_template/last-modified/plugins/lib/linux-x64/libgit2-ef5a385.so
  14. BIN
      docs/_template/last-modified/plugins/lib/osx/libgit2-ef5a385.dylib
  15. BIN
      docs/_template/last-modified/plugins/lib/rhel-x64/libgit2-ef5a385.so
  16. BIN
      docs/_template/last-modified/plugins/lib/ubuntu.16.04-arm64/libgit2-ef5a385.so
  17. BIN
      docs/_template/last-modified/plugins/lib/ubuntu.18.04-x64/libgit2-ef5a385.so
  18. BIN
      docs/_template/last-modified/plugins/lib/win32/x64/git2-ef5a385.dll
  19. BIN
      docs/_template/last-modified/plugins/lib/win32/x86/git2-ef5a385.dll
  20. +2
    -1
      docs/_template/light-dark-theme/partials/affix.tmpl.partial
  21. +5
    -2
      docs/faq/basics/getting-started.md
  22. BIN
      docs/faq/basics/images/role-copy.png
  23. +1
    -0
      src/Discord.Net.Commands/Discord.Net.Commands.csproj
  24. +3
    -2
      src/Discord.Net.Commands/ModuleBase.cs
  25. +5
    -1
      src/Discord.Net.Core/Audio/IAudioClient.cs
  26. +22
    -1
      src/Discord.Net.Core/CDN.cs
  27. +1
    -0
      src/Discord.Net.Core/Discord.Net.Core.csproj
  28. +1
    -1
      src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs
  29. +6
    -3
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  30. +9
    -0
      src/Discord.Net.Core/Entities/Channels/INewsChannel.cs
  31. +22
    -0
      src/Discord.Net.Core/Entities/Gateway/BotGateway.cs
  32. +38
    -0
      src/Discord.Net.Core/Entities/Gateway/SessionStartLimit.cs
  33. +21
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildWidgetProperties.cs
  34. +165
    -37
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  35. +12
    -0
      src/Discord.Net.Core/Entities/IApplication.cs
  36. +1
    -0
      src/Discord.Net.Core/Entities/IUpdateable.cs
  37. +8
    -0
      src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs
  38. +10
    -3
      src/Discord.Net.Core/Entities/Messages/IMessage.cs
  39. +8
    -1
      src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
  40. +25
    -2
      src/Discord.Net.Core/Entities/Messages/MessageReference.cs
  41. +7
    -0
      src/Discord.Net.Core/Entities/Messages/MessageType.cs
  42. +1
    -1
      src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
  43. +4
    -0
      src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
  44. +9
    -2
      src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
  45. +17
    -5
      src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs
  46. +27
    -0
      src/Discord.Net.Core/Entities/Teams/ITeam.cs
  47. +25
    -0
      src/Discord.Net.Core/Entities/Teams/ITeamMember.cs
  48. +11
    -0
      src/Discord.Net.Core/Entities/Teams/MembershipState.cs
  49. +1
    -1
      src/Discord.Net.Core/Entities/Users/IGuildUser.cs
  50. +20
    -0
      src/Discord.Net.Core/Extensions/MessageExtensions.cs
  51. +10
    -0
      src/Discord.Net.Core/IDiscordClient.cs
  52. +1
    -0
      src/Discord.Net.Core/RequestOptions.cs
  53. +7
    -1
      src/Discord.Net.Rest/API/Common/Application.cs
  54. +22
    -2
      src/Discord.Net.Rest/API/Common/Guild.cs
  55. +2
    -2
      src/Discord.Net.Rest/API/Common/GuildEmbed.cs
  56. +13
    -0
      src/Discord.Net.Rest/API/Common/GuildWidget.cs
  57. +9
    -0
      src/Discord.Net.Rest/API/Common/MembershipState.cs
  58. +2
    -0
      src/Discord.Net.Rest/API/Common/Message.cs
  59. +1
    -1
      src/Discord.Net.Rest/API/Common/MessageReference.cs
  60. +16
    -0
      src/Discord.Net.Rest/API/Common/SessionStartLimit.cs
  61. +17
    -0
      src/Discord.Net.Rest/API/Common/Team.cs
  62. +17
    -0
      src/Discord.Net.Rest/API/Common/TeamMember.cs
  63. +2
    -0
      src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs
  64. +3
    -1
      src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs
  65. +3
    -1
      src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs
  66. +5
    -1
      src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs
  67. +14
    -0
      src/Discord.Net.Rest/API/Rest/ModifyGuildWidgetParams.cs
  68. +1
    -0
      src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
  69. +3
    -0
      src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs
  70. +7
    -3
      src/Discord.Net.Rest/BaseDiscordClient.cs
  71. +30
    -5
      src/Discord.Net.Rest/ClientHelper.cs
  72. +1
    -0
      src/Discord.Net.Rest/Discord.Net.Rest.csproj
  73. +36
    -5
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  74. +14
    -6
      src/Discord.Net.Rest/DiscordRestClient.cs
  75. +29
    -23
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  76. +8
    -5
      src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs
  77. +12
    -12
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  78. +12
    -12
      src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
  79. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestNewsChannel.cs
  80. +12
    -12
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  81. +28
    -6
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  82. +177
    -26
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  83. +25
    -0
      src/Discord.Net.Rest/Entities/Guilds/RestGuildWidget.cs
  84. +2
    -0
      src/Discord.Net.Rest/Entities/Messages/AllowedMentions.cs
  85. +1
    -1
      src/Discord.Net.Rest/Entities/Messages/Attachment.cs
  86. +24
    -5
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  87. +5
    -2
      src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
  88. +25
    -10
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  89. +10
    -0
      src/Discord.Net.Rest/Entities/RestApplication.cs
  90. +37
    -0
      src/Discord.Net.Rest/Entities/Teams/RestTeam.cs
  91. +30
    -0
      src/Discord.Net.Rest/Entities/Teams/RestTeamMember.cs
  92. +10
    -0
      src/Discord.Net.Rest/Extensions/EntityExtensions.cs
  93. +53
    -0
      src/Discord.Net.Rest/Net/Queue/GatewayBucket.cs
  94. +37
    -7
      src/Discord.Net.Rest/Net/Queue/RequestQueue.cs
  95. +111
    -9
      src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs
  96. +3
    -3
      src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs
  97. +2
    -0
      src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
  98. +31
    -0
      src/Discord.Net.WebSocket/API/Gateway/InviteCreateEvent.cs
  99. +14
    -0
      src/Discord.Net.WebSocket/API/Gateway/InviteDeleteEvent.cs
  100. +1
    -1
      src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs

+ 2
- 3
.gitignore View File

@@ -127,7 +127,7 @@ publish/
# Publish Web Output # Publish Web Output
*.[Pp]ublish.xml *.[Pp]ublish.xml
*.azurePubxml *.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted # but database connection strings (with potential passwords) will be unencrypted
*.pubxml *.pubxml
*.publishproj *.publishproj
@@ -151,7 +151,6 @@ AppPackages/
# Others # Others
*.[Cc]ache *.[Cc]ache
ClientBin/ ClientBin/
[Ss]tyle[Cc]op.*
~$* ~$*
*~ *~
*.dbmdl *.dbmdl
@@ -206,4 +205,4 @@ docs/api/\.manifest
\.idea/ \.idea/


# Codealike UID # Codealike UID
codealike.json
codealike.json

+ 3
- 3
Discord.Net.targets View File

@@ -5,11 +5,11 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Authors>Discord.Net Contributors</Authors> <Authors>Discord.Net Contributors</Authors>
<PackageTags>discord;discordapp</PackageTags> <PackageTags>discord;discordapp</PackageTags>
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>
<PackageProjectUrl>https://github.com/Discord-Net/Discord.Net</PackageProjectUrl>
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> <PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl>
<PackageIconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</PackageIconUrl>
<PackageIconUrl>https://github.com/Discord-Net/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</PackageIconUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl>
<RepositoryUrl>git://github.com/Discord-Net/Discord.Net</RepositoryUrl>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(BuildNumber)' == '' "> <PropertyGroup Condition=" '$(BuildNumber)' == '' ">
<VersionSuffix Condition=" '$(VersionSuffix)' != ''">$(VersionSuffix)-dev</VersionSuffix> <VersionSuffix Condition=" '$(VersionSuffix)' != ''">$(VersionSuffix)-dev</VersionSuffix>


+ 1
- 1
README.md View File

@@ -4,7 +4,7 @@
[![Build Status](https://dev.azure.com/discord-net/Discord.Net/_apis/build/status/discord-net.Discord.Net?branchName=dev)](https://dev.azure.com/discord-net/Discord.Net/_build/latest?definitionId=1&branchName=dev) [![Build Status](https://dev.azure.com/discord-net/Discord.Net/_apis/build/status/discord-net.Discord.Net?branchName=dev)](https://dev.azure.com/discord-net/Discord.Net/_build/latest?definitionId=1&branchName=dev)
[![Discord](https://discord.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/jkrBmQR) [![Discord](https://discord.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/jkrBmQR)


An unofficial .NET API Wrapper for the Discord client (http://discordapp.com).
An unofficial .NET API Wrapper for the Discord client (https://discord.com).


Check out the [documentation](https://discord.foxbot.me/) or join the [Discord API Chat](https://discord.gg/jkrBmQR). Check out the [documentation](https://discord.foxbot.me/) or join the [Discord API Chat](https://discord.gg/jkrBmQR).




+ 9
- 0
StyleAnalyzer.targets View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.205" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>

BIN
docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll View File


BIN
docs/_template/last-modified/plugins/LibGit2Sharp.dll View File


+ 3
- 3
docs/_template/last-modified/plugins/LibGit2Sharp.dll.config View File

@@ -1,4 +1,4 @@
<configuration>
<dllmap os="linux" cpu="x86-64" wordsize="64" dll="git2-a904fc6" target="lib/linux-x64/libgit2-a904fc6.so" />
<dllmap os="osx" cpu="x86,x86-64" dll="git2-a904fc6" target="lib/osx/libgit2-a904fc6.dylib" />
<configuration>
<dllmap os="linux" cpu="x86-64" wordsize="64" dll="git2-ef5a385" target="lib/linux-x64/libgit2-ef5a385.so" />
<dllmap os="osx" cpu="x86,x86-64" dll="git2-ef5a385" target="lib/osx/libgit2-ef5a385.dylib" />
</configuration> </configuration>

BIN
docs/_template/last-modified/plugins/lib/alpine-x64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/alpine.3.9-x64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/debian-arm64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/debian.9-x64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/fedora-x64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/linux-x64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/osx/libgit2-ef5a385.dylib View File


BIN
docs/_template/last-modified/plugins/lib/rhel-x64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/ubuntu.16.04-arm64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/ubuntu.18.04-x64/libgit2-ef5a385.so View File


BIN
docs/_template/last-modified/plugins/lib/win32/x64/git2-ef5a385.dll View File


BIN
docs/_template/last-modified/plugins/lib/win32/x86/git2-ef5a385.dll View File


+ 2
- 1
docs/_template/light-dark-theme/partials/affix.tmpl.partial View File

@@ -27,7 +27,8 @@
</div> </div>
{{/_disableContribution}} {{/_disableContribution}}
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix"> <nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
<!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
<h5>{{__global.inThisArticle}}</h5>
<div></div>
</nav> </nav>
</div> </div>
</div> </div>

+ 5
- 2
docs/faq/basics/getting-started.md View File

@@ -74,6 +74,9 @@ it will return the user ID of the aforementioned user.


Several common ways to do this: Several common ways to do this:


1. Make the role mentionable and mention the role, and escape it
1. (Easiest) Right click on the role either in the Server Settings
or in the user's role list.
![Roles](images/role-copy.png)
2. Make the role mentionable and mention the role, and escape it
using the `\` character in front. using the `\` character in front.
2. Inspect the roles collection within the guild via your debugger.
3. Inspect the roles collection within the guild via your debugger.

BIN
docs/faq/basics/images/role-copy.png View File

Before After
Width: 384  |  Height: 530  |  Size: 16 KiB

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

@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" /> <Import Project="../../Discord.Net.targets" />
<Import Project="../../StyleAnalyzer.targets"/>
<PropertyGroup> <PropertyGroup>
<AssemblyName>Discord.Net.Commands</AssemblyName> <AssemblyName>Discord.Net.Commands</AssemblyName>
<RootNamespace>Discord.Commands</RootNamespace> <RootNamespace>Discord.Commands</RootNamespace>


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

@@ -35,9 +35,10 @@ namespace Discord.Commands
/// Specifies if notifications are sent for mentioned users and roles in the <paramref name="message"/>. /// Specifies if notifications are sent for mentioned users and roles in the <paramref name="message"/>.
/// If <c>null</c>, all mentioned roles and users will be notified. /// If <c>null</c>, all mentioned roles and users will be notified.
/// </param> /// </param>
protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null)
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
{ {
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// The method to execute before executing the command. /// The method to execute before executing the command.


+ 5
- 1
src/Discord.Net.Core/Audio/IAudioClient.cs View File

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


namespace Discord.Audio namespace Discord.Audio
@@ -20,6 +21,9 @@ namespace Discord.Audio
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the voice UDP server. </summary> /// <summary> Gets the estimated round-trip latency, in milliseconds, to the voice UDP server. </summary>
int UdpLatency { get; } int UdpLatency { get; }


/// <summary>Gets the current audio streams.</summary>
IReadOnlyDictionary<ulong, AudioInStream> GetStreams();

Task StopAsync(); Task StopAsync();
Task SetSpeakingAsync(bool value); Task SetSpeakingAsync(bool value);




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

@@ -7,6 +7,17 @@ namespace Discord
/// </summary> /// </summary>
public static class CDN public static class CDN
{ {
/// <summary>
/// Returns a team icon URL.
/// </summary>
/// <param name="teamId">The team identifier.</param>
/// <param name="iconId">The icon identifier.</param>
/// <returns>
/// A URL pointing to the team's icon.
/// </returns>
public static string GetTeamIconUrl(ulong teamId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}team-icons/{teamId}/{iconId}.jpg" : null;

/// <summary> /// <summary>
/// Returns an application icon URL. /// Returns an application icon URL.
/// </summary> /// </summary>
@@ -62,11 +73,21 @@ namespace Discord
/// <param name="guildId">The guild snowflake identifier.</param> /// <param name="guildId">The guild snowflake identifier.</param>
/// <param name="splashId">The splash icon identifier.</param> /// <param name="splashId">The splash icon identifier.</param>
/// <returns> /// <returns>
/// A URL pointing to the guild's icon.
/// A URL pointing to the guild's splash.
/// </returns> /// </returns>
public static string GetGuildSplashUrl(ulong guildId, string splashId) public static string GetGuildSplashUrl(ulong guildId, string splashId)
=> splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null; => splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null;
/// <summary> /// <summary>
/// Returns a guild discovery splash URL.
/// </summary>
/// <param name="guildId">The guild snowflake identifier.</param>
/// <param name="discoverySplashId">The discovery splash icon identifier.</param>
/// <returns>
/// A URL pointing to the guild's discovery splash.
/// </returns>
public static string GetGuildDiscoverySplashUrl(ulong guildId, string discoverySplashId)
=> discoverySplashId != null ? $"{DiscordConfig.CDNUrl}discovery-splashes/{guildId}/{discoverySplashId}.jpg" : null;
/// <summary>
/// Returns a channel icon URL. /// Returns a channel icon URL.
/// </summary> /// </summary>
/// <param name="channelId">The channel snowflake identifier.</param> /// <param name="channelId">The channel snowflake identifier.</param>


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

@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" /> <Import Project="../../Discord.Net.targets" />
<Import Project="../../StyleAnalyzer.targets"/>
<PropertyGroup> <PropertyGroup>
<AssemblyName>Discord.Net.Core</AssemblyName> <AssemblyName>Discord.Net.Core</AssemblyName>
<RootNamespace>Discord</RootNamespace> <RootNamespace>Discord</RootNamespace>


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

@@ -28,7 +28,7 @@ namespace Discord
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Setting this value to a category's snowflake identifier will change or set this channel's parent to the /// Setting this value to a category's snowflake identifier will change or set this channel's parent to the
/// specified channel; setting this value to <c>0</c> will detach this channel from its parent if one
/// specified channel; setting this value to <see langword="null"/> will detach this channel from its parent if one
/// is set. /// is set.
/// </remarks> /// </remarks>
public Optional<ulong?> CategoryId { get; set; } public Optional<ulong?> CategoryId { get; set; }


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

@@ -27,11 +27,12 @@ namespace Discord
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified. /// If <c>null</c>, all mentioned roles and users will be notified.
/// </param> /// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <returns> /// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result /// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message. /// contains the sent message.
/// </returns> /// </returns>
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null);
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
/// <summary> /// <summary>
/// Sends a file to this message channel with an optional caption. /// Sends a file to this message channel with an optional caption.
/// </summary> /// </summary>
@@ -63,11 +64,12 @@ namespace Discord
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified. /// If <c>null</c>, all mentioned roles and users will be notified.
/// </param> /// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <returns> /// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result /// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message. /// contains the sent message.
/// </returns> /// </returns>
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
/// <summary> /// <summary>
/// Sends a file to this message channel with an optional caption. /// Sends a file to this message channel with an optional caption.
/// </summary> /// </summary>
@@ -96,11 +98,12 @@ namespace Discord
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified. /// If <c>null</c>, all mentioned roles and users will be notified.
/// </param> /// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <returns> /// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result /// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message. /// contains the sent message.
/// </returns> /// </returns>
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null);


/// <summary> /// <summary>
/// Gets a message from this message channel. /// Gets a message from this message channel.


+ 9
- 0
src/Discord.Net.Core/Entities/Channels/INewsChannel.cs View File

@@ -0,0 +1,9 @@
namespace Discord
{
/// <summary>
/// Represents a generic news channel in a guild that can send and receive messages.
/// </summary>
public interface INewsChannel : ITextChannel
{
}
}

+ 22
- 0
src/Discord.Net.Core/Entities/Gateway/BotGateway.cs View File

@@ -0,0 +1,22 @@
namespace Discord
{
/// <summary>
/// Stores the gateway information related to the current bot.
/// </summary>
public class BotGateway
{
/// <summary>
/// Gets the WSS URL that can be used for connecting to the gateway.
/// </summary>
public string Url { get; internal set; }
/// <summary>
/// Gets the recommended number of shards to use when connecting.
/// </summary>
public int Shards { get; internal set; }
/// <summary>
/// Gets the <see cref="SessionStartLimit"/> that contains the information
/// about the current session start limit.
/// </summary>
public SessionStartLimit SessionStartLimit { get; internal set; }
}
}

+ 38
- 0
src/Discord.Net.Core/Entities/Gateway/SessionStartLimit.cs View File

@@ -0,0 +1,38 @@
namespace Discord
{
/// <summary>
/// Stores the information related to the gateway identify request.
/// </summary>
public class SessionStartLimit
{
/// <summary>
/// Gets the total number of session starts the current user is allowed.
/// </summary>
/// <returns>
/// The maximum amount of session starts the current user is allowed.
/// </returns>
public int Total { get; internal set; }
/// <summary>
/// Gets the remaining number of session starts the current user is allowed.
/// </summary>
/// <returns>
/// The remaining amount of session starts the current user is allowed.
/// </returns>
public int Remaining { get; internal set; }
/// <summary>
/// Gets the number of milliseconds after which the limit resets.
/// </summary>
/// <returns>
/// The milliseconds until the limit resets back to the <see cref="Total"/>.
/// </returns>
public int ResetAfter { get; internal set; }
/// <summary>
/// Gets the maximum concurrent identify requests in a time window.
/// </summary>
/// <returns>
/// The maximum concurrent identify requests in a time window,
/// limited to the same rate limit key.
/// </returns>
public int MaxConcurrency { get; internal set; }
}
}

+ 21
- 0
src/Discord.Net.Core/Entities/Guilds/GuildWidgetProperties.cs View File

@@ -0,0 +1,21 @@
namespace Discord
{
/// <summary>
/// Provides properties that are used to modify the widget of an <see cref="IGuild" /> with the specified changes.
/// </summary>
public class GuildWidgetProperties
{
/// <summary>
/// Sets whether the widget should be enabled.
/// </summary>
public Optional<bool> Enabled { get; set; }
/// <summary>
/// Sets the channel that the invite should place its users in, if not <see langword="null" />.
/// </summary>
public Optional<IChannel> Channel { get; set; }
/// <summary>
/// Sets the channel that the invite should place its users in, if not <see langword="null" />.
/// </summary>
public Optional<ulong?> ChannelId { get; set; }
}
}

+ 165
- 37
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -23,7 +23,7 @@ namespace Discord
/// automatically moved to the AFK voice channel. /// automatically moved to the AFK voice channel.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// An <see cref="int"/> representing the amount of time in seconds for a user to be marked as inactive
/// An <see langword="int"/> representing the amount of time in seconds for a user to be marked as inactive
/// and moved into the AFK voice channel. /// and moved into the AFK voice channel.
/// </returns> /// </returns>
int AFKTimeout { get; } int AFKTimeout { get; }
@@ -31,10 +31,17 @@ namespace Discord
/// Gets a value that indicates whether this guild is embeddable (i.e. can use widget). /// Gets a value that indicates whether this guild is embeddable (i.e. can use widget).
/// </summary> /// </summary>
/// <returns> /// <returns>
/// <c>true</c> if this guild can be embedded via widgets; otherwise <c>false</c>.
/// <see langword="true" /> if this guild has a widget enabled; otherwise <see langword="false" />.
/// </returns> /// </returns>
bool IsEmbeddable { get; } bool IsEmbeddable { get; }
/// <summary> /// <summary>
/// Gets a value that indicates whether this guild has the widget enabled.
/// </summary>
/// <returns>
/// <see langword="true" /> if this guild has a widget enabled; otherwise <see langword="false" />.
/// </returns>
bool IsWidgetEnabled { get; }
/// <summary>
/// Gets the default message notifications for users who haven't explicitly set their notification settings. /// Gets the default message notifications for users who haven't explicitly set their notification settings.
/// </summary> /// </summary>
DefaultMessageNotifications DefaultMessageNotifications { get; } DefaultMessageNotifications DefaultMessageNotifications { get; }
@@ -64,31 +71,45 @@ namespace Discord
/// Gets the ID of this guild's icon. /// Gets the ID of this guild's icon.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// An identifier for the splash image; <c>null</c> if none is set.
/// An identifier for the splash image; <see langword="null" /> if none is set.
/// </returns> /// </returns>
string IconId { get; } string IconId { get; }
/// <summary> /// <summary>
/// Gets the URL of this guild's icon. /// Gets the URL of this guild's icon.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A URL pointing to the guild's icon; <c>null</c> if none is set.
/// A URL pointing to the guild's icon; <see langword="null" /> if none is set.
/// </returns> /// </returns>
string IconUrl { get; } string IconUrl { get; }
/// <summary> /// <summary>
/// Gets the ID of this guild's splash image. /// Gets the ID of this guild's splash image.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// An identifier for the splash image; <c>null</c> if none is set.
/// An identifier for the splash image; <see langword="null" /> if none is set.
/// </returns> /// </returns>
string SplashId { get; } string SplashId { get; }
/// <summary> /// <summary>
/// Gets the URL of this guild's splash image. /// Gets the URL of this guild's splash image.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A URL pointing to the guild's splash image; <c>null</c> if none is set.
/// A URL pointing to the guild's splash image; <see langword="null" /> if none is set.
/// </returns> /// </returns>
string SplashUrl { get; } string SplashUrl { get; }
/// <summary> /// <summary>
/// Gets the ID of this guild's discovery splash image.
/// </summary>
/// <returns>
/// An identifier for the discovery splash image; <see langword="null" /> if none is set.
/// </returns>
string DiscoverySplashId { get; }
/// <summary>
/// Gets the URL of this guild's discovery splash image.
/// </summary>
/// <returns>
/// A URL pointing to the guild's discovery splash image; <see langword="null" /> if none is set.
/// </returns>
string DiscoverySplashUrl { get; }
/// <summary>
/// Determines if this guild is currently connected and ready to be used. /// Determines if this guild is currently connected and ready to be used.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
@@ -98,7 +119,7 @@ namespace Discord
/// This boolean is used to determine if the guild is currently connected to the WebSocket and is ready to be used/accessed. /// This boolean is used to determine if the guild is currently connected to the WebSocket and is ready to be used/accessed.
/// </remarks> /// </remarks>
/// <returns> /// <returns>
/// <c>true</c> if this guild is currently connected and ready to be used; otherwise <c>false</c>.
/// <c>true</c> if this guild is currently connected and ready to be used; otherwise <see langword="false"/>.
/// </returns> /// </returns>
bool Available { get; } bool Available { get; }


@@ -106,7 +127,7 @@ namespace Discord
/// Gets the ID of the AFK voice channel for this guild. /// Gets the ID of the AFK voice channel for this guild.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="ulong"/> representing the snowflake identifier of the AFK voice channel; <c>null</c> if
/// A <see langword="ulong"/> representing the snowflake identifier of the AFK voice channel; <see langword="null" /> if
/// none is set. /// none is set.
/// </returns> /// </returns>
ulong? AFKChannelId { get; } ulong? AFKChannelId { get; }
@@ -121,7 +142,7 @@ namespace Discord
/// </note> /// </note>
/// </remarks> /// </remarks>
/// <returns> /// <returns>
/// A <see cref="ulong"/> representing the snowflake identifier of the default text channel; <c>0</c> if
/// A <see langword="ulong"/> representing the snowflake identifier of the default text channel; <c>0</c> if
/// none can be found. /// none can be found.
/// </returns> /// </returns>
ulong DefaultChannelId { get; } ulong DefaultChannelId { get; }
@@ -129,30 +150,54 @@ namespace Discord
/// Gets the ID of the widget embed channel of this guild. /// Gets the ID of the widget embed channel of this guild.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="ulong"/> representing the snowflake identifier of the embedded channel found within the
/// widget settings of this guild; <c>null</c> if none is set.
/// A <see langword="ulong"/> representing the snowflake identifier of the embedded channel found within the
/// widget settings of this guild; <see langword="null" /> if none is set.
/// </returns> /// </returns>
ulong? EmbedChannelId { get; } ulong? EmbedChannelId { get; }
/// <summary> /// <summary>
/// Gets the ID of the channel assigned to the widget of this guild.
/// </summary>
/// <returns>
/// A <see langword="ulong"/> representing the snowflake identifier of the channel assigned to the widget found
/// within the widget settings of this guild; <see langword="null" /> if none is set.
/// </returns>
ulong? WidgetChannelId { get; }
/// <summary>
/// Gets the ID of the channel where randomized welcome messages are sent. /// Gets the ID of the channel where randomized welcome messages are sent.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="ulong"/> representing the snowflake identifier of the system channel where randomized
/// welcome messages are sent; <c>null</c> if none is set.
/// A <see langword="ulong"/> representing the snowflake identifier of the system channel where randomized
/// welcome messages are sent; <see langword="null" /> if none is set.
/// </returns> /// </returns>
ulong? SystemChannelId { get; } ulong? SystemChannelId { get; }
/// <summary> /// <summary>
/// Gets the ID of the channel with the rules.
/// </summary>
/// <returns>
/// A <see langword="ulong"/> representing the snowflake identifier of the channel that contains the rules;
/// <see langword="null" /> if none is set.
/// </returns>
ulong? RulesChannelId { get; }
/// <summary>
/// Gets the ID of the channel where admins and moderators of Community guilds receive notices from Discord.
/// </summary>
/// <returns>
/// A <see langword="ulong"/> representing the snowflake identifier of the channel where admins and moderators
/// of Community guilds receive notices from Discord; <see langword="null" /> if none is set.
/// </returns>
ulong? PublicUpdatesChannelId { get; }
/// <summary>
/// Gets the ID of the user that owns this guild. /// Gets the ID of the user that owns this guild.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="ulong"/> representing the snowflake identifier of the user that owns this guild.
/// A <see langword="ulong"/> representing the snowflake identifier of the user that owns this guild.
/// </returns> /// </returns>
ulong OwnerId { get; } ulong OwnerId { get; }
/// <summary> /// <summary>
/// Gets the application ID of the guild creator if it is bot-created. /// Gets the application ID of the guild creator if it is bot-created.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="ulong"/> representing the snowflake identifier of the application ID that created this guild, or <c>null</c> if it was not bot-created.
/// A <see langword="ulong"/> representing the snowflake identifier of the application ID that created this guild, or <see langword="null" /> if it was not bot-created.
/// </returns> /// </returns>
ulong? ApplicationId { get; } ulong? ApplicationId { get; }
/// <summary> /// <summary>
@@ -208,21 +253,21 @@ namespace Discord
/// Gets the identifier for this guilds banner image. /// Gets the identifier for this guilds banner image.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// An identifier for the banner image; <c>null</c> if none is set.
/// An identifier for the banner image; <see langword="null" /> if none is set.
/// </returns> /// </returns>
string BannerId { get; } string BannerId { get; }
/// <summary> /// <summary>
/// Gets the URL of this guild's banner image. /// Gets the URL of this guild's banner image.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A URL pointing to the guild's banner image; <c>null</c> if none is set.
/// A URL pointing to the guild's banner image; <see langword="null" /> if none is set.
/// </returns> /// </returns>
string BannerUrl { get; } string BannerUrl { get; }
/// <summary> /// <summary>
/// Gets the code for this guild's vanity invite URL. /// Gets the code for this guild's vanity invite URL.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A string containing the vanity invite code for this guild; <c>null</c> if none is set.
/// A string containing the vanity invite code for this guild; <see langword="null" /> if none is set.
/// </returns> /// </returns>
string VanityURLCode { get; } string VanityURLCode { get; }
/// <summary> /// <summary>
@@ -236,7 +281,7 @@ namespace Discord
/// Gets the description for the guild. /// Gets the description for the guild.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// The description for the guild; <c>null</c> if none is set.
/// The description for the guild; <see langword="null" /> if none is set.
/// </returns> /// </returns>
string Description { get; } string Description { get; }
/// <summary> /// <summary>
@@ -246,9 +291,50 @@ namespace Discord
/// This is the number of users who have boosted this guild. /// This is the number of users who have boosted this guild.
/// </remarks> /// </remarks>
/// <returns> /// <returns>
/// The number of premium subscribers of this guild.
/// The number of premium subscribers of this guild; <see langword="null" /> if not available.
/// </returns> /// </returns>
int PremiumSubscriptionCount { get; } int PremiumSubscriptionCount { get; }
/// <summary>
/// Gets the maximum number of presences for the guild.
/// </summary>
/// <returns>
/// The maximum number of presences for the guild.
/// </returns>
int? MaxPresences { get; }
/// <summary>
/// Gets the maximum number of members for the guild.
/// </summary>
/// <returns>
/// The maximum number of members for the guild.
/// </returns>
int? MaxMembers { get; }
/// <summary>
/// Gets the maximum amount of users in a video channel.
/// </summary>
/// <returns>
/// The maximum amount of users in a video channel.
/// </returns>
int? MaxVideoChannelUsers { get; }
/// <summary>
/// Gets the approximate number of members in this guild.
/// </summary>
/// <remarks>
/// Only available when getting a guild via REST when `with_counts` is true.
/// </remarks>
/// <returns>
/// The approximate number of members in this guild.
/// </returns>
int? ApproximateMemberCount { get; }
/// <summary>
/// Gets the approximate number of non-offline members in this guild.
/// </summary>
/// <remarks>
/// Only available when getting a guild via REST when `with_counts` is true.
/// </remarks>
/// <returns>
/// The approximate number of non-offline members in this guild.
/// </returns>
int? ApproximatePresenceCount { get; }


/// <summary> /// <summary>
/// Gets the preferred locale of this guild in IETF BCP 47 /// Gets the preferred locale of this guild in IETF BCP 47
@@ -285,8 +371,18 @@ namespace Discord
/// <returns> /// <returns>
/// A task that represents the asynchronous modification operation. /// A task that represents the asynchronous modification operation.
/// </returns> /// </returns>
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")]
Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null); Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null);
/// <summary> /// <summary>
/// Modifies this guild's widget.
/// </summary>
/// <param name="func">The delegate containing the properties to modify the guild widget with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null);
/// <summary>
/// Bulk-modifies the order of channels in this guild. /// Bulk-modifies the order of channels in this guild.
/// </summary> /// </summary>
/// <param name="args">The properties used to modify the channel positions with.</param> /// <param name="args">The properties used to modify the channel positions with.</param>
@@ -336,7 +432,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains a ban object, which /// A task that represents the asynchronous get operation. The task result contains a ban object, which
/// contains the user information and the reason for the ban; <c>null</c> if the ban entry cannot be found.
/// contains the user information and the reason for the ban; <see langword="null" /> if the ban entry cannot be found.
/// </returns> /// </returns>
Task<IBan> GetBanAsync(IUser user, RequestOptions options = null); Task<IBan> GetBanAsync(IUser user, RequestOptions options = null);
/// <summary> /// <summary>
@@ -346,7 +442,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains a ban object, which /// A task that represents the asynchronous get operation. The task result contains a ban object, which
/// contains the user information and the reason for the ban; <c>null</c> if the ban entry cannot be found.
/// contains the user information and the reason for the ban; <see langword="null" /> if the ban entry cannot be found.
/// </returns> /// </returns>
Task<IBan> GetBanAsync(ulong userId, RequestOptions options = null); Task<IBan> GetBanAsync(ulong userId, RequestOptions options = null);
/// <summary> /// <summary>
@@ -410,7 +506,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the generic channel /// A task that represents the asynchronous get operation. The task result contains the generic channel
/// associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// associated with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns> /// </returns>
Task<IGuildChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IGuildChannel> GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
@@ -431,7 +527,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel /// A task that represents the asynchronous get operation. The task result contains the text channel
/// associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// associated with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns> /// </returns>
Task<ITextChannel> GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<ITextChannel> GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
@@ -462,7 +558,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the voice channel associated /// A task that represents the asynchronous get operation. The task result contains the voice channel associated
/// with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns> /// </returns>
Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
@@ -472,7 +568,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the voice channel that the /// A task that represents the asynchronous get operation. The task result contains the voice channel that the
/// AFK users will be moved to after they have idled for too long; <c>null</c> if none is set.
/// AFK users will be moved to after they have idled for too long; <see langword="null" /> if none is set.
/// </returns> /// </returns>
Task<IVoiceChannel> GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IVoiceChannel> GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
@@ -482,7 +578,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel where /// A task that represents the asynchronous get operation. The task result contains the text channel where
/// randomized welcome messages will be sent to; <c>null</c> if none is set.
/// randomized welcome messages will be sent to; <see langword="null" /> if none is set.
/// </returns> /// </returns>
Task<ITextChannel> GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<ITextChannel> GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
@@ -492,7 +588,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the first viewable text /// A task that represents the asynchronous get operation. The task result contains the first viewable text
/// channel in this guild; <c>null</c> if none is found.
/// channel in this guild; <see langword="null" /> if none is found.
/// </returns> /// </returns>
Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
@@ -502,9 +598,40 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the embed channel set /// A task that represents the asynchronous get operation. The task result contains the embed channel set
/// within the server's widget settings; <c>null</c> if none is set.
/// within the server's widget settings; <see langword="null" /> if none is set.
/// </returns> /// </returns>
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")]
Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the widget channel set
/// within the server's widget settings; <see langword="null" /> if none is set.
/// </returns>
Task<IGuildChannel> GetWidgetChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the text channel where Community guilds can display rules and/or guidelines.
/// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel
/// where Community guilds can display rules and/or guidelines; <see langword="null" /> if none is set.
/// </returns>
Task<ITextChannel> GetRulesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the text channel channel where admins and moderators of Community guilds receive notices from Discord.
/// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel channel where
/// admins and moderators of Community guilds receive notices from Discord; <see langword="null" /> if none is set.
/// </returns>
Task<ITextChannel> GetPublicUpdatesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);


/// <summary> /// <summary>
/// Creates a new text channel in this guild. /// Creates a new text channel in this guild.
@@ -573,7 +700,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the partial metadata of /// A task that represents the asynchronous get operation. The task result contains the partial metadata of
/// the vanity invite found within this guild; <c>null</c> if none is found.
/// the vanity invite found within this guild; <see langword="null" /> if none is found.
/// </returns> /// </returns>
Task<IInviteMetadata> GetVanityInviteAsync(RequestOptions options = null); Task<IInviteMetadata> GetVanityInviteAsync(RequestOptions options = null);


@@ -582,7 +709,7 @@ namespace Discord
/// </summary> /// </summary>
/// <param name="id">The snowflake identifier for the role.</param> /// <param name="id">The snowflake identifier for the role.</param>
/// <returns> /// <returns>
/// A role that is associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// A role that is associated with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns> /// </returns>
IRole GetRole(ulong id); IRole GetRole(ulong id);
/// <summary> /// <summary>
@@ -624,7 +751,7 @@ namespace Discord
/// <param name="accessToken">The OAuth2 access token for the user, requested with the guilds.join scope.</param> /// <param name="accessToken">The OAuth2 access token for the user, requested with the guilds.join scope.</param>
/// <param name="func">The delegate containing the properties to be applied to the user upon being added to the guild.</param> /// <param name="func">The delegate containing the properties to be applied to the user upon being added to the guild.</param>
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns>A guild user associated with the specified <paramref name="userId" />; <c>null</c> if the user is already in the guild.</returns>
/// <returns>A guild user associated with the specified <paramref name="userId" />; <see langword="null" /> if the user is already in the guild.</returns>
Task<IGuildUser> AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null); Task<IGuildUser> AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets a collection of all users in this guild. /// Gets a collection of all users in this guild.
@@ -649,7 +776,7 @@ namespace Discord
/// <remarks> /// <remarks>
/// This method retrieves a user found within this guild. /// This method retrieves a user found within this guild.
/// <note> /// <note>
/// This may return <c>null</c> in the WebSocket implementation due to incomplete user collection in
/// This may return <see langword="null" /> in the WebSocket implementation due to incomplete user collection in
/// large guilds. /// large guilds.
/// </note> /// </note>
/// </remarks> /// </remarks>
@@ -658,7 +785,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the guild user /// A task that represents the asynchronous get operation. The task result contains the guild user
/// associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// associated with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns> /// </returns>
Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
@@ -705,11 +832,12 @@ namespace Discord
/// <param name="days">The number of days required for the users to be kicked.</param> /// <param name="days">The number of days required for the users to be kicked.</param>
/// <param name="simulate">Whether this prune action is a simulation.</param> /// <param name="simulate">Whether this prune action is a simulation.</param>
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <param name="includeRoleIds">An array of role IDs to be included in the prune of users who do not have any additional roles.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous prune operation. The task result contains the number of users to /// A task that represents the asynchronous prune operation. The task result contains the number of users to
/// be or has been removed from this guild. /// be or has been removed from this guild.
/// </returns> /// </returns>
Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null);
Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null, IEnumerable<ulong> includeRoleIds = null);
/// <summary> /// <summary>
/// Gets a collection of users in this guild that the name or nickname starts with the /// Gets a collection of users in this guild that the name or nickname starts with the
/// provided <see cref="string"/> at <paramref name="query"/>. /// provided <see cref="string"/> at <paramref name="query"/>.
@@ -751,7 +879,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the webhook with the /// A task that represents the asynchronous get operation. The task result contains the webhook with the
/// specified <paramref name="id"/>; <c>null</c> if none is found.
/// specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns> /// </returns>
Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null); Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null);
/// <summary> /// <summary>
@@ -771,7 +899,7 @@ namespace Discord
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the emote found with the /// A task that represents the asynchronous get operation. The task result contains the emote found with the
/// specified <paramref name="id"/>; <c>null</c> if none is found.
/// specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns> /// </returns>
Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null); Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null);
/// <summary> /// <summary>


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

@@ -22,6 +22,18 @@ namespace Discord
/// Gets the icon URL of the application. /// Gets the icon URL of the application.
/// </summary> /// </summary>
string IconUrl { get; } string IconUrl { get; }
/// <summary>
/// Gets if the bot is public.
/// </summary>
bool IsBotPublic { get; }
/// <summary>
/// Gets if the bot requires code grant.
/// </summary>
bool BotRequiresCodeGrant { get; }
/// <summary>
/// Gets the team associated with this application if there is one.
/// </summary>
ITeam Team { get; }


/// <summary> /// <summary>
/// Gets the partial user object containing info on the owner of the application. /// Gets the partial user object containing info on the owner of the application.


+ 1
- 0
src/Discord.Net.Core/Entities/IUpdateable.cs View File

@@ -10,6 +10,7 @@ namespace Discord
/// <summary> /// <summary>
/// Updates this object's properties with its current state. /// Updates this object's properties with its current state.
/// </summary> /// </summary>
/// <param name="options">The options to be used when sending the request.</param>
Task UpdateAsync(RequestOptions options = null); Task UpdateAsync(RequestOptions options = null);
} }
} }

+ 8
- 0
src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs View File

@@ -49,6 +49,14 @@ namespace Discord
/// </summary> /// </summary>
public List<ulong> UserIds { get; set; } = new List<ulong>(); public List<ulong> UserIds { get; set; } = new List<ulong>();


/// <summary>
/// Gets or sets whether to mention the author of the message you are replying to or not.
/// </summary>
/// <remarks>
/// Specifically for inline replies.
/// </remarks>
public bool? MentionRepliedUser { get; set; } = null;

/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AllowedMentions"/> class. /// Initializes a new instance of the <see cref="AllowedMentions"/> class.
/// </summary> /// </summary>


+ 10
- 3
src/Discord.Net.Core/Entities/Messages/IMessage.cs View File

@@ -39,6 +39,13 @@ namespace Discord
/// </returns> /// </returns>
bool IsSuppressed { get; } bool IsSuppressed { get; }
/// <summary> /// <summary>
/// Gets the value that indicates whether this message mentioned everyone.
/// </summary>
/// <returns>
/// <c>true</c> if this message mentioned everyone; otherwise <c>false</c>.
/// </returns>
bool MentionedEveryone { get; }
/// <summary>
/// Gets the content for this message. /// Gets the content for this message.
/// </summary> /// </summary>
/// <returns> /// <returns>
@@ -141,11 +148,11 @@ namespace Discord
MessageApplication Application { get; } MessageApplication Application { get; }


/// <summary> /// <summary>
/// Gets the reference to the original message if it was crossposted.
/// Gets the reference to the original message if it is a crosspost, channel follow add, pin, or reply message.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Sent with Cross-posted messages, meaning they were published from news channels
/// and received by subscriber channels.
/// Sent with cross-posted messages, meaning they were published from news channels
/// and received by subscriber channels, channel follow adds, pins, and message replies.
/// </remarks> /// </remarks>
/// <returns> /// <returns>
/// A message's reference, if any is associated. /// A message's reference, if any is associated.


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

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


namespace Discord namespace Discord
@@ -9,6 +8,14 @@ namespace Discord
/// </summary> /// </summary>
public interface IUserMessage : IMessage public interface IUserMessage : IMessage
{ {
/// <summary>
/// Gets the referenced message if it is a crosspost, channel follow add, pin, or reply message.
/// </summary>
/// <returns>
/// The referenced message, if any is associated and still exists.
/// </returns>
IUserMessage ReferencedMessage { get; }

/// <summary> /// <summary>
/// Modifies this message. /// Modifies this message.
/// </summary> /// </summary>


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

@@ -3,7 +3,7 @@ using System.Diagnostics;
namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
/// Contains the IDs sent from a crossposted message.
/// Contains the IDs sent from a crossposted message or inline reply.
/// </summary> /// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class MessageReference public class MessageReference
@@ -16,13 +16,36 @@ namespace Discord
/// <summary> /// <summary>
/// Gets the Channel ID of the original message. /// Gets the Channel ID of the original message.
/// </summary> /// </summary>
public ulong ChannelId { get; internal set; }
/// <remarks>
/// It only will be the default value (zero) if it was instantiated with a <see langword="null"/> in the constructor.
/// </remarks>
public ulong ChannelId { get => InternalChannelId.GetValueOrDefault(); }
internal Optional<ulong> InternalChannelId;


/// <summary> /// <summary>
/// Gets the Guild ID of the original message. /// Gets the Guild ID of the original message.
/// </summary> /// </summary>
public Optional<ulong> GuildId { get; internal set; } public Optional<ulong> GuildId { get; internal set; }


/// <summary>
/// Initializes a new instance of the <see cref="MessageReference"/> class.
/// </summary>
/// <param name="messageId">
/// The ID of the message that will be referenced. Used to reply to specific messages and the only parameter required for it.
/// </param>
/// <param name="channelId">
/// The ID of the channel that will be referenced. It will be validated if sent.
/// </param>
/// <param name="guildId">
/// The ID of the guild that will be referenced. It will be validated if sent.
/// </param>
public MessageReference(ulong? messageId = null, ulong? channelId = null, ulong? guildId = null)
{
MessageId = messageId ?? Optional.Create<ulong>();
InternalChannelId = channelId ?? Optional.Create<ulong>();
GuildId = guildId ?? Optional.Create<ulong>();
}

private string DebuggerDisplay private string DebuggerDisplay
=> $"Channel ID: ({ChannelId}){(GuildId.IsSpecified ? $", Guild ID: ({GuildId.Value})" : "")}" + => $"Channel ID: ({ChannelId}){(GuildId.IsSpecified ? $", Guild ID: ({GuildId.Value})" : "")}" +
$"{(MessageId.IsSpecified ? $", Message ID: ({MessageId.Value})" : "")}"; $"{(MessageId.IsSpecified ? $", Message ID: ({MessageId.Value})" : "")}";


+ 7
- 0
src/Discord.Net.Core/Entities/Messages/MessageType.cs View File

@@ -57,5 +57,12 @@ namespace Discord
/// The message for when a news channel subscription is added to a text channel. /// The message for when a news channel subscription is added to a text channel.
/// </summary> /// </summary>
ChannelFollowAdd = 12, ChannelFollowAdd = 12,
/// <summary>
/// The message is an inline reply.
/// </summary>
/// <remarks>
/// Only available in API v8.
/// </remarks>
Reply = 19,
} }
} }

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

@@ -17,7 +17,7 @@ namespace Discord
/// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for category channels. </summary> /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for category channels. </summary>
public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001);
/// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for direct message channels. </summary> /// <summary> Gets a <see cref="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_1011100110001_000000);
/// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for group channels. </summary> /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for group channels. </summary>
public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000); public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000);
/// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for a given channel type. </summary> /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for a given channel type. </summary>


+ 4
- 0
src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs View File

@@ -51,6 +51,10 @@ namespace Discord
/// authentication when used on a guild that has server-wide 2FA enabled. /// authentication when used on a guild that has server-wide 2FA enabled.
/// </remarks> /// </remarks>
ManageGuild = 0x00_00_00_20, ManageGuild = 0x00_00_00_20,
/// <summary>
/// Allows for viewing of guild insights
/// </summary>
ViewGuildInsights = 0x00_08_00_00,


// Text // Text
/// <summary> /// <summary>


+ 9
- 2
src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs View File

@@ -12,7 +12,7 @@ namespace Discord
/// <summary> Gets a <see cref="GuildPermissions"/> that grants all guild permissions for webhook users. </summary> /// <summary> Gets a <see cref="GuildPermissions"/> that grants all guild permissions for webhook users. </summary>
public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000);
/// <summary> Gets a <see cref="GuildPermissions"/> that grants all guild permissions. </summary> /// <summary> Gets a <see cref="GuildPermissions"/> that grants all guild permissions. </summary>
public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111111111_111111);
public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111111_1111111111111_111111);


/// <summary> Gets a packed value representing all the permissions in this <see cref="GuildPermissions"/>. </summary> /// <summary> Gets a packed value representing all the permissions in this <see cref="GuildPermissions"/>. </summary>
public ulong RawValue { get; } public ulong RawValue { get; }
@@ -34,6 +34,8 @@ namespace Discord
public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions);
/// <summary> If <c>true</c>, a user may view the audit log. </summary> /// <summary> If <c>true</c>, a user may view the audit log. </summary>
public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog); public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog);
/// <summary> If <c>true</c>, a user may view the guild insights. </summary>
public bool ViewGuildInsights => Permissions.GetValue(RawValue, GuildPermission.ViewGuildInsights);


/// <summary> If True, a user may join channels. </summary> /// <summary> If True, a user may join channels. </summary>
[Obsolete("Use ViewChannel instead.")] [Obsolete("Use ViewChannel instead.")]
@@ -97,6 +99,7 @@ namespace Discord
bool? manageGuild = null, bool? manageGuild = null,
bool? addReactions = null, bool? addReactions = null,
bool? viewAuditLog = null, bool? viewAuditLog = null,
bool? viewGuildInsights = null,
bool? viewChannel = null, bool? viewChannel = null,
bool? sendMessages = null, bool? sendMessages = null,
bool? sendTTSMessages = null, bool? sendTTSMessages = null,
@@ -130,6 +133,7 @@ namespace Discord
Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild); Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild);
Permissions.SetValue(ref value, addReactions, GuildPermission.AddReactions); Permissions.SetValue(ref value, addReactions, GuildPermission.AddReactions);
Permissions.SetValue(ref value, viewAuditLog, GuildPermission.ViewAuditLog); Permissions.SetValue(ref value, viewAuditLog, GuildPermission.ViewAuditLog);
Permissions.SetValue(ref value, viewGuildInsights, GuildPermission.ViewGuildInsights);
Permissions.SetValue(ref value, viewChannel, GuildPermission.ViewChannel); Permissions.SetValue(ref value, viewChannel, GuildPermission.ViewChannel);
Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages); Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages);
Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages);
@@ -166,6 +170,7 @@ namespace Discord
bool manageGuild = false, bool manageGuild = false,
bool addReactions = false, bool addReactions = false,
bool viewAuditLog = false, bool viewAuditLog = false,
bool viewGuildInsights = false,
bool viewChannel = false, bool viewChannel = false,
bool sendMessages = false, bool sendMessages = false,
bool sendTTSMessages = false, bool sendTTSMessages = false,
@@ -198,6 +203,7 @@ namespace Discord
manageGuild: manageGuild, manageGuild: manageGuild,
addReactions: addReactions, addReactions: addReactions,
viewAuditLog: viewAuditLog, viewAuditLog: viewAuditLog,
viewGuildInsights: viewGuildInsights,
viewChannel: viewChannel, viewChannel: viewChannel,
sendMessages: sendMessages, sendMessages: sendMessages,
sendTTSMessages: sendTTSMessages, sendTTSMessages: sendTTSMessages,
@@ -231,6 +237,7 @@ namespace Discord
bool? manageGuild = null, bool? manageGuild = null,
bool? addReactions = null, bool? addReactions = null,
bool? viewAuditLog = null, bool? viewAuditLog = null,
bool? viewGuildInsights = null,
bool? viewChannel = null, bool? viewChannel = null,
bool? sendMessages = null, bool? sendMessages = null,
bool? sendTTSMessages = null, bool? sendTTSMessages = null,
@@ -254,7 +261,7 @@ namespace Discord
bool? manageWebhooks = null, bool? manageWebhooks = null,
bool? manageEmojis = null) bool? manageEmojis = null)
=> new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions,
viewAuditLog, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles,
viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles,
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers,
useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis);




+ 17
- 5
src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs View File

@@ -76,6 +76,10 @@ namespace Discord
public PermValue MoveMembers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MoveMembers); public PermValue MoveMembers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MoveMembers);
/// <summary> If Allowed, a user may use voice-activity-detection rather than push-to-talk. </summary> /// <summary> If Allowed, a user may use voice-activity-detection rather than push-to-talk. </summary>
public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD);
/// <summary> If Allowed, a user may use priority speaker in a voice channel. </summary>
public PermValue PrioritySpeaker => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.PrioritySpeaker);
/// <summary> If Allowed, a user may go live in a voice channel. </summary>
public PermValue Stream => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.Stream);


/// <summary> If Allowed, a user may adjust role permissions. This also implicitly grants all other permissions. </summary> /// <summary> If Allowed, a user may adjust role permissions. This also implicitly grants all other permissions. </summary>
public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles); public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles);
@@ -109,7 +113,9 @@ namespace Discord
PermValue? moveMembers = null, PermValue? moveMembers = null,
PermValue? useVoiceActivation = null, PermValue? useVoiceActivation = null,
PermValue? manageRoles = null, PermValue? manageRoles = null,
PermValue? manageWebhooks = null)
PermValue? manageWebhooks = null,
PermValue? prioritySpeaker = null,
PermValue? stream = null)
{ {
Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite);
Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannels); Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannels);
@@ -129,6 +135,8 @@ namespace Discord
Permissions.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers); Permissions.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers);
Permissions.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers); Permissions.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers);
Permissions.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD); Permissions.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD);
Permissions.SetValue(ref allowValue, ref denyValue, prioritySpeaker, ChannelPermission.PrioritySpeaker);
Permissions.SetValue(ref allowValue, ref denyValue, stream, ChannelPermission.Stream);
Permissions.SetValue(ref allowValue, ref denyValue, manageRoles, ChannelPermission.ManageRoles); Permissions.SetValue(ref allowValue, ref denyValue, manageRoles, ChannelPermission.ManageRoles);
Permissions.SetValue(ref allowValue, ref denyValue, manageWebhooks, ChannelPermission.ManageWebhooks); Permissions.SetValue(ref allowValue, ref denyValue, manageWebhooks, ChannelPermission.ManageWebhooks);


@@ -159,10 +167,12 @@ namespace Discord
PermValue moveMembers = PermValue.Inherit, PermValue moveMembers = PermValue.Inherit,
PermValue useVoiceActivation = PermValue.Inherit, PermValue useVoiceActivation = PermValue.Inherit,
PermValue manageRoles = PermValue.Inherit, PermValue manageRoles = PermValue.Inherit,
PermValue manageWebhooks = PermValue.Inherit)
PermValue manageWebhooks = PermValue.Inherit,
PermValue prioritySpeaker = PermValue.Inherit,
PermValue stream = PermValue.Inherit)
: this(0, 0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, : this(0, 0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages,
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers,
moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { }
moveMembers, useVoiceActivation, manageRoles, manageWebhooks, prioritySpeaker, stream) { }


/// <summary> /// <summary>
/// Initializes a new <see cref="OverwritePermissions" /> from the current one, changing the provided /// Initializes a new <see cref="OverwritePermissions" /> from the current one, changing the provided
@@ -188,10 +198,12 @@ namespace Discord
PermValue? moveMembers = null, PermValue? moveMembers = null,
PermValue? useVoiceActivation = null, PermValue? useVoiceActivation = null,
PermValue? manageRoles = null, PermValue? manageRoles = null,
PermValue? manageWebhooks = null)
PermValue? manageWebhooks = null,
PermValue? prioritySpeaker = null,
PermValue? stream = null)
=> new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages,
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers,
moveMembers, useVoiceActivation, manageRoles, manageWebhooks);
moveMembers, useVoiceActivation, manageRoles, manageWebhooks, prioritySpeaker, stream);


/// <summary> /// <summary>
/// Creates a <see cref="List{T}"/> of all the <see cref="ChannelPermission"/> values that are allowed. /// Creates a <see cref="List{T}"/> of all the <see cref="ChannelPermission"/> values that are allowed.


+ 27
- 0
src/Discord.Net.Core/Entities/Teams/ITeam.cs View File

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

namespace Discord
{
/// <summary>
/// Represents a Discord Team.
/// </summary>
public interface ITeam
{
/// <summary>
/// Gets the team icon url.
/// </summary>
string IconUrl { get; }
/// <summary>
/// Gets the team unique identifier.
/// </summary>
ulong Id { get; }
/// <summary>
/// Gets the members of this team.
/// </summary>
IReadOnlyList<ITeamMember> TeamMembers { get; }
/// <summary>
/// Gets the user identifier that owns this team.
/// </summary>
ulong OwnerUserId { get; }
}
}

+ 25
- 0
src/Discord.Net.Core/Entities/Teams/ITeamMember.cs View File

@@ -0,0 +1,25 @@
namespace Discord
{
/// <summary>
/// Represents a Discord Team member.
/// </summary>
public interface ITeamMember
{
/// <summary>
/// Gets the membership state of this team member.
/// </summary>
MembershipState MembershipState { get; }
/// <summary>
/// Gets the permissions of this team member.
/// </summary>
string[] Permissions { get; }
/// <summary>
/// Gets the team unique identifier for this team member.
/// </summary>
ulong TeamId { get; }
/// <summary>
/// Gets the Discord user of this team member.
/// </summary>
IUser User { get; }
}
}

+ 11
- 0
src/Discord.Net.Core/Entities/Teams/MembershipState.cs View File

@@ -0,0 +1,11 @@
namespace Discord
{
/// <summary>
/// Represents the membership state of a team member.
/// </summary>
public enum MembershipState
{
Invited,
Accepted,
}
}

+ 1
- 1
src/Discord.Net.Core/Entities/Users/IGuildUser.cs View File

@@ -73,7 +73,7 @@ namespace Discord
/// </summary> /// </summary>
/// <example> /// <example>
/// <para>The following example checks if the current user has the ability to send a message with attachment in /// <para>The following example checks if the current user has the ability to send a message with attachment in
/// this channel; if so, uploads a file via <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions)"/>.</para>
/// this channel; if so, uploads a file via <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference)"/>.</para>
/// <code language="cs"> /// <code language="cs">
/// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles) /// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles)
/// await targetChannel.SendFileAsync("fortnite.png"); /// await targetChannel.SendFileAsync("fortnite.png");


+ 20
- 0
src/Discord.Net.Core/Extensions/MessageExtensions.cs View File

@@ -71,5 +71,25 @@ namespace Discord
foreach (var rxn in reactions) foreach (var rxn in reactions)
await msg.RemoveReactionAsync(rxn, user, options).ConfigureAwait(false); await msg.RemoveReactionAsync(rxn, user, options).ConfigureAwait(false);
} }

/// <summary>
/// Sends an inline reply that references a message.
/// </summary>
/// <param name="text">The message to be sent.</param>
/// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param>
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param>
/// <param name="allowedMentions">
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified.
/// </param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
public static async Task<IUserMessage> ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null)
{
return await msg.Channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id)).ConfigureAwait(false);
}
} }
} }

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

@@ -274,5 +274,15 @@ namespace Discord
/// that represents the number of shards that should be used with this account. /// that represents the number of shards that should be used with this account.
/// </returns> /// </returns>
Task<int> GetRecommendedShardCountAsync(RequestOptions options = null); Task<int> GetRecommendedShardCountAsync(RequestOptions options = null);

/// <summary>
/// Gets the gateway information related to the bot.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a <see cref="BotGateway"/>
/// that represents the gateway information related to the bot.
/// </returns>
Task<BotGateway> GetBotGatewayAsync(RequestOptions options = null);
} }
} }

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

@@ -61,6 +61,7 @@ namespace Discord
internal BucketId BucketId { get; set; } internal BucketId BucketId { get; set; }
internal bool IsClientBucket { get; set; } internal bool IsClientBucket { get; set; }
internal bool IsReactionBucket { get; set; } internal bool IsReactionBucket { get; set; }
internal bool IsGatewayBucket { get; set; }


internal static RequestOptions CreateOrClone(RequestOptions options) internal static RequestOptions CreateOrClone(RequestOptions options)
{ {


+ 7
- 1
src/Discord.Net.Rest/API/Common/Application.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
@@ -15,6 +15,12 @@ namespace Discord.API
public ulong Id { get; set; } public ulong Id { get; set; }
[JsonProperty("icon")] [JsonProperty("icon")]
public string Icon { get; set; } public string Icon { get; set; }
[JsonProperty("bot_public")]
public bool IsBotPublic { get; set; }
[JsonProperty("bot_require_code_grant")]
public bool BotRequiresCodeGrant { get; set; }
[JsonProperty("team")]
public Team Team { get; set; }


[JsonProperty("flags"), Int53] [JsonProperty("flags"), Int53]
public Optional<ulong> Flags { get; set; } public Optional<ulong> Flags { get; set; }


+ 22
- 2
src/Discord.Net.Rest/API/Common/Guild.cs View File

@@ -13,6 +13,8 @@ namespace Discord.API
public string Icon { get; set; } public string Icon { get; set; }
[JsonProperty("splash")] [JsonProperty("splash")]
public string Splash { get; set; } public string Splash { get; set; }
[JsonProperty("discovery_splash")]
public string DiscoverySplash { get; set; }
[JsonProperty("owner_id")] [JsonProperty("owner_id")]
public ulong OwnerId { get; set; } public ulong OwnerId { get; set; }
[JsonProperty("region")] [JsonProperty("region")]
@@ -22,9 +24,9 @@ namespace Discord.API
[JsonProperty("afk_timeout")] [JsonProperty("afk_timeout")]
public int AFKTimeout { get; set; } public int AFKTimeout { get; set; }
[JsonProperty("embed_enabled")] [JsonProperty("embed_enabled")]
public bool EmbedEnabled { get; set; }
public Optional<bool> EmbedEnabled { get; set; }
[JsonProperty("embed_channel_id")] [JsonProperty("embed_channel_id")]
public ulong? EmbedChannelId { get; set; }
public Optional<ulong?> EmbedChannelId { get; set; }
[JsonProperty("verification_level")] [JsonProperty("verification_level")]
public VerificationLevel VerificationLevel { get; set; } public VerificationLevel VerificationLevel { get; set; }
[JsonProperty("default_message_notifications")] [JsonProperty("default_message_notifications")]
@@ -43,6 +45,10 @@ namespace Discord.API
public MfaLevel MfaLevel { get; set; } public MfaLevel MfaLevel { get; set; }
[JsonProperty("application_id")] [JsonProperty("application_id")]
public ulong? ApplicationId { get; set; } public ulong? ApplicationId { get; set; }
[JsonProperty("widget_enabled")]
public Optional<bool> WidgetEnabled { get; set; }
[JsonProperty("widget_channel_id")]
public Optional<ulong?> WidgetChannelId { get; set; }
[JsonProperty("system_channel_id")] [JsonProperty("system_channel_id")]
public ulong? SystemChannelId { get; set; } public ulong? SystemChannelId { get; set; }
[JsonProperty("premium_tier")] [JsonProperty("premium_tier")]
@@ -56,9 +62,23 @@ namespace Discord.API
// this value is inverted, flags set will turn OFF features // this value is inverted, flags set will turn OFF features
[JsonProperty("system_channel_flags")] [JsonProperty("system_channel_flags")]
public SystemChannelMessageDeny SystemChannelFlags { get; set; } public SystemChannelMessageDeny SystemChannelFlags { get; set; }
[JsonProperty("rules_channel_id")]
public ulong? RulesChannelId { get; set; }
[JsonProperty("max_presences")]
public Optional<int?> MaxPresences { get; set; }
[JsonProperty("max_members")]
public Optional<int> MaxMembers { get; set; }
[JsonProperty("premium_subscription_count")] [JsonProperty("premium_subscription_count")]
public int? PremiumSubscriptionCount { get; set; } public int? PremiumSubscriptionCount { get; set; }
[JsonProperty("preferred_locale")] [JsonProperty("preferred_locale")]
public string PreferredLocale { get; set; } public string PreferredLocale { get; set; }
[JsonProperty("public_updates_channel_id")]
public ulong? PublicUpdatesChannelId { get; set; }
[JsonProperty("max_video_channel_users")]
public Optional<int> MaxVideoChannelUsers { get; set; }
[JsonProperty("approximate_member_count")]
public Optional<int> ApproximateMemberCount { get; set; }
[JsonProperty("approximate_presence_count")]
public Optional<int> ApproximatePresenceCount { get; set; }
} }
} }

+ 2
- 2
src/Discord.Net.Rest/API/Common/GuildEmbed.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
@@ -8,6 +8,6 @@ namespace Discord.API
[JsonProperty("enabled")] [JsonProperty("enabled")]
public bool Enabled { get; set; } public bool Enabled { get; set; }
[JsonProperty("channel_id")] [JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
public ulong? ChannelId { get; set; }
} }
} }

+ 13
- 0
src/Discord.Net.Rest/API/Common/GuildWidget.cs View File

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

namespace Discord.API
{
internal class GuildWidget
{
[JsonProperty("enabled")]
public bool Enabled { get; set; }
[JsonProperty("channel_id")]
public ulong? ChannelId { get; set; }
}
}

+ 9
- 0
src/Discord.Net.Rest/API/Common/MembershipState.cs View File

@@ -0,0 +1,9 @@
namespace Discord.API
{
internal enum MembershipState
{
None = 0,
Invited = 1,
Accepted = 2,
}
}

+ 2
- 0
src/Discord.Net.Rest/API/Common/Message.cs View File

@@ -56,5 +56,7 @@ namespace Discord.API
public Optional<MessageFlags> Flags { get; set; } public Optional<MessageFlags> Flags { get; set; }
[JsonProperty("allowed_mentions")] [JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("referenced_message")]
public Optional<Message> ReferencedMessage { get; set; }
} }
} }

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

@@ -8,7 +8,7 @@ namespace Discord.API
public Optional<ulong> MessageId { get; set; } public Optional<ulong> MessageId { get; set; }


[JsonProperty("channel_id")] [JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
public Optional<ulong> ChannelId { get; set; } // Optional when sending, always present when receiving


[JsonProperty("guild_id")] [JsonProperty("guild_id")]
public Optional<ulong> GuildId { get; set; } public Optional<ulong> GuildId { get; set; }


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

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

namespace Discord.API.Rest
{
internal class SessionStartLimit
{
[JsonProperty("total")]
public int Total { get; set; }
[JsonProperty("remaining")]
public int Remaining { get; set; }
[JsonProperty("reset_after")]
public int ResetAfter { get; set; }
[JsonProperty("max_concurrency")]
public int MaxConcurrency { get; set; }
}
}

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

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

namespace Discord.API
{
internal class Team
{
[JsonProperty("icon")]
public Optional<string> Icon { get; set; }
[JsonProperty("id")]
public ulong Id { get; set; }
[JsonProperty("members")]
public TeamMember[] TeamMembers { get; set; }
[JsonProperty("owner_user_id")]
public ulong OwnerUserId { get; set; }
}
}

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

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

namespace Discord.API
{
internal class TeamMember
{
[JsonProperty("membership_state")]
public MembershipState MembershipState { get; set; }
[JsonProperty("permissions")]
public string[] Permissions { get; set; }
[JsonProperty("team_id")]
public ulong TeamId { get; set; }
[JsonProperty("user")]
public User User { get; set; }
}
}

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

@@ -17,6 +17,8 @@ namespace Discord.API.Rest
public Optional<Embed> Embed { get; set; } public Optional<Embed> Embed { get; set; }
[JsonProperty("allowed_mentions")] [JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("message_reference")]
public Optional<MessageReference> MessageReference { get; set; }


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


+ 3
- 1
src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.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
@@ -19,6 +19,8 @@ namespace Discord.API.Rest
public Optional<string> Username { get; set; } public Optional<string> Username { get; set; }
[JsonProperty("avatar_url")] [JsonProperty("avatar_url")]
public Optional<string> AvatarUrl { get; set; } public Optional<string> AvatarUrl { get; set; }
[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }


public CreateWebhookMessageParams(string content) public CreateWebhookMessageParams(string content)
{ {


+ 3
- 1
src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.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
@@ -9,5 +9,7 @@ namespace Discord.API.Rest
public string Url { get; set; } public string Url { get; set; }
[JsonProperty("shards")] [JsonProperty("shards")]
public int Shards { get; set; } public int Shards { get; set; }
[JsonProperty("session_start_limit")]
public SessionStartLimit SessionStartLimit { get; set; }
} }
} }

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

@@ -9,9 +9,13 @@ namespace Discord.API.Rest
[JsonProperty("days")] [JsonProperty("days")]
public int Days { get; } public int Days { get; }


public GuildPruneParams(int days)
[JsonProperty("include_roles")]
public ulong[] IncludeRoleIds { get; }

public GuildPruneParams(int days, ulong[] includeRoleIds)
{ {
Days = days; Days = days;
IncludeRoleIds = includeRoleIds;
} }
} }
} }

+ 14
- 0
src/Discord.Net.Rest/API/Rest/ModifyGuildWidgetParams.cs View File

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

namespace Discord.API.Rest
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class ModifyGuildWidgetParams
{
[JsonProperty("enabled")]
public Optional<bool> Enabled { get; set; }
[JsonProperty("channel")]
public Optional<ulong?> ChannelId { get; set; }
}
}

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

@@ -20,6 +20,7 @@ namespace Discord.API.Rest
public Optional<bool> IsTTS { get; set; } public Optional<bool> IsTTS { get; set; }
public Optional<Embed> Embed { get; set; } public Optional<Embed> Embed { get; set; }
public Optional<AllowedMentions> AllowedMentions { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; }
public Optional<MessageReference> MessageReference { get; set; }
public bool IsSpoiler { get; set; } = false; public bool IsSpoiler { get; set; } = false;


public UploadFileParams(Stream file) public UploadFileParams(Stream file)


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

@@ -21,6 +21,7 @@ namespace Discord.API.Rest
public Optional<string> Username { get; set; } public Optional<string> Username { get; set; }
public Optional<string> AvatarUrl { get; set; } public Optional<string> AvatarUrl { get; set; }
public Optional<Embed[]> Embeds { get; set; } public Optional<Embed[]> Embeds { get; set; }
public Optional<AllowedMentions> AllowedMentions { get; set; }


public bool IsSpoiler { get; set; } = false; public bool IsSpoiler { get; set; } = false;


@@ -51,6 +52,8 @@ namespace Discord.API.Rest
payload["avatar_url"] = AvatarUrl.Value; payload["avatar_url"] = AvatarUrl.Value;
if (Embeds.IsSpecified) if (Embeds.IsSpecified)
payload["embeds"] = Embeds.Value; payload["embeds"] = Embeds.Value;
if (AllowedMentions.IsSpecified)
payload["allowed_mentions"] = AllowedMentions.Value;


var json = new StringBuilder(); var json = new StringBuilder();
using (var text = new StringWriter(json)) using (var text = new StringWriter(json))


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

@@ -46,12 +46,12 @@ namespace Discord.Rest
_restLogger = LogManager.CreateLogger("Rest"); _restLogger = LogManager.CreateLogger("Rest");
_isFirstLogin = config.DisplayInitialLog; _isFirstLogin = config.DisplayInitialLog;


ApiClient.RequestQueue.RateLimitTriggered += async (id, info) =>
ApiClient.RequestQueue.RateLimitTriggered += async (id, info, endpoint) =>
{ {
if (info == null) if (info == null)
await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {id?.ToString() ?? "null"}").ConfigureAwait(false);
await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false);
else else
await _restLogger.WarningAsync($"Rate limit triggered: {id?.ToString() ?? "null"}").ConfigureAwait(false);
await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false);
}; };
ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false);
} }
@@ -152,6 +152,10 @@ namespace Discord.Rest
public Task<int> GetRecommendedShardCountAsync(RequestOptions options = null) public Task<int> GetRecommendedShardCountAsync(RequestOptions options = null)
=> ClientHelper.GetRecommendShardCountAsync(this, options); => ClientHelper.GetRecommendShardCountAsync(this, options);


/// <inheritdoc />
public Task<BotGateway> GetBotGatewayAsync(RequestOptions options = null)
=> ClientHelper.GetBotGatewayAsync(this, options);

//IDiscordClient //IDiscordClient
/// <inheritdoc /> /// <inheritdoc />
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;


+ 30
- 5
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -62,9 +62,9 @@ namespace Discord.Rest
} }
public static async Task<RestGuild> GetGuildAsync(BaseDiscordClient client, public static async Task<RestGuild> GetGuildAsync(BaseDiscordClient client,
ulong id, RequestOptions options)
ulong id, bool withCounts, RequestOptions options)
{ {
var model = await client.ApiClient.GetGuildAsync(id, options).ConfigureAwait(false);
var model = await client.ApiClient.GetGuildAsync(id, withCounts, options).ConfigureAwait(false);
if (model != null) if (model != null)
return RestGuild.Create(client, model); return RestGuild.Create(client, model);
return null; return null;
@@ -77,6 +77,14 @@ namespace Discord.Rest
return RestGuildEmbed.Create(model); return RestGuildEmbed.Create(model);
return null; return null;
} }
public static async Task<RestGuildWidget?> GetGuildWidgetAsync(BaseDiscordClient client,
ulong id, RequestOptions options)
{
var model = await client.ApiClient.GetGuildWidgetAsync(id, options).ConfigureAwait(false);
if (model != null)
return RestGuildWidget.Create(model);
return null;
}
public static IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(BaseDiscordClient client, public static IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(BaseDiscordClient client,
ulong? fromGuildId, int? limit, RequestOptions options) ulong? fromGuildId, int? limit, RequestOptions options)
{ {
@@ -106,13 +114,13 @@ namespace Discord.Rest
count: limit count: limit
); );
} }
public static async Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(BaseDiscordClient client, RequestOptions options)
public static async Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(BaseDiscordClient client, bool withCounts, RequestOptions options)
{ {
var summaryModels = await GetGuildSummariesAsync(client, null, null, options).FlattenAsync().ConfigureAwait(false); var summaryModels = await GetGuildSummariesAsync(client, null, null, options).FlattenAsync().ConfigureAwait(false);
var guilds = ImmutableArray.CreateBuilder<RestGuild>(); var guilds = ImmutableArray.CreateBuilder<RestGuild>();
foreach (var summaryModel in summaryModels) foreach (var summaryModel in summaryModels)
{ {
var guildModel = await client.ApiClient.GetGuildAsync(summaryModel.Id).ConfigureAwait(false);
var guildModel = await client.ApiClient.GetGuildAsync(summaryModel.Id, withCounts).ConfigureAwait(false);
if (guildModel != null) if (guildModel != null)
guilds.Add(RestGuild.Create(client, guildModel)); guilds.Add(RestGuild.Create(client, guildModel));
} }
@@ -140,7 +148,7 @@ namespace Discord.Rest
public static async Task<RestGuildUser> GetGuildUserAsync(BaseDiscordClient client, public static async Task<RestGuildUser> GetGuildUserAsync(BaseDiscordClient client,
ulong guildId, ulong id, RequestOptions options) ulong guildId, ulong id, RequestOptions options)
{ {
var guild = await GetGuildAsync(client, guildId, options).ConfigureAwait(false);
var guild = await GetGuildAsync(client, guildId, false, options).ConfigureAwait(false);
if (guild == null) if (guild == null)
return null; return null;


@@ -176,5 +184,22 @@ namespace Discord.Rest
var response = await client.ApiClient.GetBotGatewayAsync(options).ConfigureAwait(false); var response = await client.ApiClient.GetBotGatewayAsync(options).ConfigureAwait(false);
return response.Shards; return response.Shards;
} }

public static async Task<BotGateway> GetBotGatewayAsync(BaseDiscordClient client, RequestOptions options)
{
var response = await client.ApiClient.GetBotGatewayAsync(options).ConfigureAwait(false);
return new BotGateway
{
Url = response.Url,
Shards = response.Shards,
SessionStartLimit = new SessionStartLimit
{
Total = response.SessionStartLimit.Total,
Remaining = response.SessionStartLimit.Remaining,
ResetAfter = response.SessionStartLimit.ResetAfter,
MaxConcurrency = response.SessionStartLimit.MaxConcurrency
}
};
}
} }
} }

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

@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" /> <Import Project="../../Discord.Net.targets" />
<Import Project="../../StyleAnalyzer.targets"/>
<PropertyGroup> <PropertyGroup>
<AssemblyName>Discord.Net.Rest</AssemblyName> <AssemblyName>Discord.Net.Rest</AssemblyName>
<RootNamespace>Discord.Rest</RootNamespace> <RootNamespace>Discord.Rest</RootNamespace>


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

@@ -787,7 +787,7 @@ namespace Discord.API
} }


//Guilds //Guilds
public async Task<Guild> GetGuildAsync(ulong guildId, RequestOptions options = null)
public async Task<Guild> GetGuildAsync(ulong guildId, bool withCounts, RequestOptions options = null)
{ {
Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
@@ -795,7 +795,7 @@ namespace Discord.API
try try
{ {
var ids = new BucketIds(guildId: guildId); var ids = new BucketIds(guildId: guildId);
return await SendAsync<Guild>("GET", () => $"guilds/{guildId}", ids, options: options).ConfigureAwait(false);
return await SendAsync<Guild>("GET", () => $"guilds/{guildId}?with_counts={(withCounts ? "true" : "false")}", ids, options: options).ConfigureAwait(false);
} }
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; }
} }
@@ -853,10 +853,11 @@ namespace Discord.API
Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotNull(args, nameof(args)); Preconditions.NotNull(args, nameof(args));
Preconditions.AtLeast(args.Days, 1, nameof(args.Days)); Preconditions.AtLeast(args.Days, 1, nameof(args.Days));
string endpointRoleIds = args.IncludeRoleIds?.Length > 0 ? $"&include_roles={string.Join(",", args.IncludeRoleIds)}" : "";
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);


var ids = new BucketIds(guildId: guildId); var ids = new BucketIds(guildId: guildId);
return await SendAsync<GetGuildPruneCountResponse>("GET", () => $"guilds/{guildId}/prune?days={args.Days}", ids, options: options).ConfigureAwait(false);
return await SendAsync<GetGuildPruneCountResponse>("GET", () => $"guilds/{guildId}/prune?days={args.Days}{endpointRoleIds}", ids, options: options).ConfigureAwait(false);
} }


//Guild Bans //Guild Bans
@@ -874,8 +875,12 @@ namespace Discord.API
Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);


var ids = new BucketIds(guildId: guildId);
return await SendAsync<Ban>("GET", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false);
try
{
var ids = new BucketIds(guildId: guildId);
return await SendAsync<Ban>("GET", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; }
} }
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// <paramref name="guildId"/> and <paramref name="userId"/> must not be equal to zero. /// <paramref name="guildId"/> and <paramref name="userId"/> must not be equal to zero.
@@ -933,6 +938,32 @@ namespace Discord.API
return await SendJsonAsync<GuildEmbed>("PATCH", () => $"guilds/{guildId}/embed", args, ids, options: options).ConfigureAwait(false); return await SendJsonAsync<GuildEmbed>("PATCH", () => $"guilds/{guildId}/embed", args, ids, options: options).ConfigureAwait(false);
} }


//Guild Widget
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception>
public async Task<GuildWidget> GetGuildWidgetAsync(ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options);

try
{
var ids = new BucketIds(guildId: guildId);
return await SendAsync<GuildWidget>("GET", () => $"guilds/{guildId}/widget", ids, options: options).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; }
}
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception>
/// <exception cref="ArgumentNullException"><paramref name="args"/> must not be <see langword="null"/>.</exception>
public async Task<GuildWidget> ModifyGuildWidgetAsync(ulong guildId, Rest.ModifyGuildWidgetParams args, RequestOptions options = null)
{
Preconditions.NotNull(args, nameof(args));
Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
return await SendJsonAsync<GuildWidget>("PATCH", () => $"guilds/{guildId}/widget", args, ids, options: options).ConfigureAwait(false);
}

//Guild Integrations //Guild Integrations
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> /// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception>
public async Task<IReadOnlyCollection<Integration>> GetGuildIntegrationsAsync(ulong guildId, RequestOptions options = null) public async Task<IReadOnlyCollection<Integration>> GetGuildIntegrationsAsync(ulong guildId, RequestOptions options = null)


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

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO; using System.IO;
@@ -28,10 +29,10 @@ namespace Discord.Rest
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }


private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider,
DiscordRestConfig.UserAgent,
rateLimitPrecision: config.RateLimitPrecision,
useSystemClock: config.UseSystemClock);
=> new API.DiscordRestApiClient(config.RestClientProvider,
DiscordRestConfig.UserAgent,
rateLimitPrecision: config.RateLimitPrecision,
useSystemClock: config.UseSystemClock);


internal override void Dispose(bool disposing) internal override void Dispose(bool disposing)
{ {
@@ -76,15 +77,22 @@ namespace Discord.Rest
=> ClientHelper.GetInviteAsync(this, inviteId, options); => ClientHelper.GetInviteAsync(this, inviteId, options);


public Task<RestGuild> GetGuildAsync(ulong id, RequestOptions options = null) public Task<RestGuild> GetGuildAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetGuildAsync(this, id, options);
=> ClientHelper.GetGuildAsync(this, id, false, options);
public Task<RestGuild> GetGuildAsync(ulong id, bool withCounts, RequestOptions options = null)
=> ClientHelper.GetGuildAsync(this, id, withCounts, options);
[Obsolete("This endpoint is deprecated, use GetGuildWidgetAsync instead.")]
public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id, RequestOptions options = null) public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetGuildEmbedAsync(this, id, options); => ClientHelper.GetGuildEmbedAsync(this, id, options);
public Task<RestGuildWidget?> GetGuildWidgetAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetGuildWidgetAsync(this, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null)
=> ClientHelper.GetGuildSummariesAsync(this, null, null, options); => ClientHelper.GetGuildSummariesAsync(this, null, null, options);
public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(ulong fromGuildId, int limit, RequestOptions options = null) public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(ulong fromGuildId, int limit, RequestOptions options = null)
=> ClientHelper.GetGuildSummariesAsync(this, fromGuildId, limit, options); => ClientHelper.GetGuildSummariesAsync(this, fromGuildId, limit, options);
public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(RequestOptions options = null)
=> ClientHelper.GetGuildsAsync(this, options);
=> ClientHelper.GetGuildsAsync(this, false, options);
public Task<IReadOnlyCollection<RestGuild>> GetGuildsAsync(bool withCounts, RequestOptions options = null)
=> ClientHelper.GetGuildsAsync(this, withCounts, options);
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null)
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options); => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options);




+ 29
- 23
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

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


namespace Discord.Rest namespace Discord.Rest
{ {
@@ -28,7 +27,16 @@ namespace Discord.Rest
{ {
Name = args.Name, Name = args.Name,
Position = args.Position, Position = args.Position,
CategoryId = args.CategoryId
CategoryId = args.CategoryId,
Overwrites = args.PermissionOverwrites.IsSpecified
? args.PermissionOverwrites.Value.Select(overwrite => new API.Overwrite
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
}; };
return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
} }
@@ -70,7 +78,16 @@ namespace Discord.Rest
Name = args.Name, Name = args.Name,
Position = args.Position, Position = args.Position,
CategoryId = args.CategoryId, CategoryId = args.CategoryId,
UserLimit = args.UserLimit.IsSpecified ? (args.UserLimit.Value ?? 0) : Optional.Create<int>()
UserLimit = args.UserLimit.IsSpecified ? (args.UserLimit.Value ?? 0) : Optional.Create<int>(),
Overwrites = args.PermissionOverwrites.IsSpecified
? args.PermissionOverwrites.Value.Select(overwrite => new API.Overwrite
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
}; };
return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
} }
@@ -112,7 +129,7 @@ namespace Discord.Rest
var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false);
if (model == null) if (model == null)
return null; return null;
var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable());
var author = MessageHelper.GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable());
return RestMessage.Create(client, channel, author, model); return RestMessage.Create(client, channel, author, model);
} }
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client,
@@ -147,7 +164,7 @@ namespace Discord.Rest
var builder = ImmutableArray.CreateBuilder<RestMessage>(); var builder = ImmutableArray.CreateBuilder<RestMessage>();
foreach (var model in models) foreach (var model in models)
{ {
var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable());
var author = MessageHelper.GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable());
builder.Add(RestMessage.Create(client, channel, author, model)); builder.Add(RestMessage.Create(client, channel, author, model));
} }
return builder.ToImmutable(); return builder.ToImmutable();
@@ -175,7 +192,7 @@ namespace Discord.Rest
var builder = ImmutableArray.CreateBuilder<RestMessage>(); var builder = ImmutableArray.CreateBuilder<RestMessage>();
foreach (var model in models) foreach (var model in models)
{ {
var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable());
var author = MessageHelper.GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable());
builder.Add(RestMessage.Create(client, channel, author, model)); builder.Add(RestMessage.Create(client, channel, author, model));
} }
return builder.ToImmutable(); return builder.ToImmutable();
@@ -183,7 +200,7 @@ namespace Discord.Rest


/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel channel, BaseDiscordClient client,
string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, RequestOptions options)
string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options)
{ {
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
@@ -204,7 +221,7 @@ namespace Discord.Rest
} }
} }


var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel() };
var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel() };
var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false);
return RestUserMessage.Create(client, channel, client.CurrentUser, model); return RestUserMessage.Create(client, channel, client.CurrentUser, model);
} }
@@ -234,16 +251,16 @@ namespace Discord.Rest
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> /// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
string filePath, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler)
string filePath, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options, bool isSpoiler)
{ {
string filename = Path.GetFileName(filePath); string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath)) using (var file = File.OpenRead(filePath))
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, allowedMentions, options, isSpoiler).ConfigureAwait(false);
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler).ConfigureAwait(false);
} }


/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
Stream stream, string filename, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler)
Stream stream, string filename, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options, bool isSpoiler)
{ {
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
@@ -264,7 +281,7 @@ namespace Discord.Rest
} }
} }


var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional<API.Embed>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsSpoiler = isSpoiler };
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional<API.Embed>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, IsSpoiler = isSpoiler };
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false);
return RestUserMessage.Create(client, channel, client.CurrentUser, model); return RestUserMessage.Create(client, channel, client.CurrentUser, model);
} }
@@ -432,16 +449,5 @@ namespace Discord.Rest
}; };
await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
} }

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

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

@@ -24,17 +24,18 @@ namespace Discord.Rest
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified. /// If <c>null</c>, all mentioned roles and users will be notified.
/// </param> /// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <returns> /// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result /// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message. /// contains the sent message.
/// </returns> /// </returns>
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null);
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
/// <summary> /// <summary>
/// Sends a file to this message channel with an optional caption. /// Sends a file to this message channel with an optional caption.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method follows the same behavior as described in /// This method follows the same behavior as described in
/// <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions)"/>. Please visit
/// <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference)"/>. Please visit
/// its documentation for more details on this method. /// its documentation for more details on this method.
/// </remarks> /// </remarks>
/// <param name="filePath">The file path of the file.</param> /// <param name="filePath">The file path of the file.</param>
@@ -47,16 +48,17 @@ namespace Discord.Rest
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified. /// If <c>null</c>, all mentioned roles and users will be notified.
/// </param> /// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <returns> /// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result /// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message. /// contains the sent message.
/// </returns> /// </returns>
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
/// <summary> /// <summary>
/// Sends a file to this message channel with an optional caption. /// Sends a file to this message channel with an optional caption.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool, AllowedMentions)"/>.
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference)"/>.
/// Please visit its documentation for more details on this method. /// Please visit its documentation for more details on this method.
/// </remarks> /// </remarks>
/// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param> /// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param>
@@ -70,11 +72,12 @@ namespace Discord.Rest
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified. /// If <c>null</c>, all mentioned roles and users will be notified.
/// </param> /// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <returns> /// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result /// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message. /// contains the sent message.
/// </returns> /// </returns>
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null);


/// <summary> /// <summary>
/// Gets a message from this message channel. /// Gets a message from this message channel.


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

@@ -93,8 +93,8 @@ namespace Discord.Rest


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options);
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options);


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
@@ -121,12 +121,12 @@ namespace Discord.Rest
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception>
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> /// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler);
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler);


/// <inheritdoc /> /// <inheritdoc />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
@@ -200,14 +200,14 @@ namespace Discord.Rest
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); => await GetPinnedMessagesAsync(options).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false);


//IChannel //IChannel
/// <inheritdoc /> /// <inheritdoc />


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

@@ -95,8 +95,8 @@ namespace Discord.Rest


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options);
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options);


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
@@ -123,12 +123,12 @@ namespace Discord.Rest
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception>
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> /// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler);
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler);


/// <inheritdoc /> /// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
@@ -178,14 +178,14 @@ namespace Discord.Rest
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); => await GetPinnedMessagesAsync(options).ConfigureAwait(false);


async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false);


async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false);


async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false);


//IAudioChannel //IAudioChannel
/// <inheritdoc /> /// <inheritdoc />


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

@@ -12,7 +12,7 @@ namespace Discord.Rest
/// Represents a REST-based news channel in a guild that has the same properties as a <see cref="RestTextChannel"/>. /// Represents a REST-based news channel in a guild that has the same properties as a <see cref="RestTextChannel"/>.
/// </summary> /// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestNewsChannel : RestTextChannel
public class RestNewsChannel : RestTextChannel, INewsChannel
{ {
internal RestNewsChannel(BaseDiscordClient discord, IGuild guild, ulong id) internal RestNewsChannel(BaseDiscordClient discord, IGuild guild, ulong id)
:base(discord, guild, id) :base(discord, guild, id)


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

@@ -102,8 +102,8 @@ namespace Discord.Rest


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options);
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options);


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
@@ -130,13 +130,13 @@ namespace Discord.Rest
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception>
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> /// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler);


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler);


/// <inheritdoc /> /// <inheritdoc />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
@@ -267,15 +267,15 @@ namespace Discord.Rest
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); => await GetPinnedMessagesAsync(options).ConfigureAwait(false);


/// <inheritdoc /> /// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false);


/// <inheritdoc /> /// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false);


//IGuildChannel //IGuildChannel
/// <inheritdoc /> /// <inheritdoc />


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

@@ -5,6 +5,7 @@ using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EmbedModel = Discord.API.GuildEmbed; using EmbedModel = Discord.API.GuildEmbed;
using WidgetModel = Discord.API.GuildWidget;
using Model = Discord.API.Guild; using Model = Discord.API.Guild;
using RoleModel = Discord.API.Role; using RoleModel = Discord.API.Role;
using ImageModel = Discord.API.Image; using ImageModel = Discord.API.Image;
@@ -99,6 +100,27 @@ namespace Discord.Rest


return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, apiArgs, options).ConfigureAwait(false); return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, apiArgs, options).ConfigureAwait(false);
} }
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
public static async Task<WidgetModel> ModifyWidgetAsync(IGuild guild, BaseDiscordClient client,
Action<GuildWidgetProperties> func, RequestOptions options)
{
if (func == null)
throw new ArgumentNullException(nameof(func));

var args = new GuildWidgetProperties();
func(args);
var apiArgs = new API.Rest.ModifyGuildWidgetParams
{
Enabled = args.Enabled
};

if (args.Channel.IsSpecified)
apiArgs.ChannelId = args.Channel.Value?.Id;
else if (args.ChannelId.IsSpecified)
apiArgs.ChannelId = args.ChannelId.Value;

return await client.ApiClient.ModifyGuildWidgetAsync(guild.Id, apiArgs, options).ConfigureAwait(false);
}
public static async Task ReorderChannelsAsync(IGuild guild, BaseDiscordClient client, public static async Task ReorderChannelsAsync(IGuild guild, BaseDiscordClient client,
IEnumerable<ReorderChannelProperties> args, RequestOptions options) IEnumerable<ReorderChannelProperties> args, RequestOptions options)
{ {
@@ -132,7 +154,7 @@ namespace Discord.Rest
public static async Task<RestBan> GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options) 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); var model = await client.ApiClient.GetGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false);
return RestBan.Create(client, model);
return model == null ? null : RestBan.Create(client, model);
} }


public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client, public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client,
@@ -404,9 +426,9 @@ namespace Discord.Rest
); );
} }
public static async Task<int> PruneUsersAsync(IGuild guild, BaseDiscordClient client, public static async Task<int> PruneUsersAsync(IGuild guild, BaseDiscordClient client,
int days, bool simulate, RequestOptions options)
int days, bool simulate, RequestOptions options, IEnumerable<ulong> includeRoleIds)
{ {
var args = new GuildPruneParams(days);
var args = new GuildPruneParams(days, includeRoleIds?.ToArray());
GetGuildPruneCountResponse model; GetGuildPruneCountResponse model;
if (simulate) if (simulate)
model = await client.ApiClient.GetGuildPruneCountAsync(guild.Id, args, options).ConfigureAwait(false); model = await client.ApiClient.GetGuildPruneCountAsync(guild.Id, args, options).ConfigureAwait(false);
@@ -479,7 +501,7 @@ namespace Discord.Rest
var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options).ConfigureAwait(false); var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options).ConfigureAwait(false);
return emote.ToEntity(); return emote.ToEntity();
} }
public static async Task<GuildEmote> CreateEmoteAsync(IGuild guild, BaseDiscordClient client, string name, Image image, Optional<IEnumerable<IRole>> roles,
public static async Task<GuildEmote> CreateEmoteAsync(IGuild guild, BaseDiscordClient client, string name, Image image, Optional<IEnumerable<IRole>> roles,
RequestOptions options) RequestOptions options)
{ {
var apiargs = new CreateGuildEmoteParams var apiargs = new CreateGuildEmoteParams
@@ -494,7 +516,7 @@ namespace Discord.Rest
return emote.ToEntity(); return emote.ToEntity();
} }
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
public static async Task<GuildEmote> ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action<EmoteProperties> func,
public static async Task<GuildEmote> ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action<EmoteProperties> func,
RequestOptions options) RequestOptions options)
{ {
if (func == null) throw new ArgumentNullException(paramName: nameof(func)); if (func == null) throw new ArgumentNullException(paramName: nameof(func));
@@ -512,7 +534,7 @@ namespace Discord.Rest
var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options).ConfigureAwait(false); var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options).ConfigureAwait(false);
return emote.ToEntity(); return emote.ToEntity();
} }
public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options)
public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options)
=> client.ApiClient.DeleteGuildEmoteAsync(guild.Id, id, options); => client.ApiClient.DeleteGuildEmoteAsync(guild.Id, id, options);
} }
} }

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

@@ -7,6 +7,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EmbedModel = Discord.API.GuildEmbed; using EmbedModel = Discord.API.GuildEmbed;
using WidgetModel = Discord.API.GuildWidget;
using Model = Discord.API.Guild; using Model = Discord.API.Guild;


namespace Discord.Rest namespace Discord.Rest
@@ -28,6 +29,8 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public bool IsEmbeddable { get; private set; } public bool IsEmbeddable { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public bool IsWidgetEnabled { get; private set; }
/// <inheritdoc />
public VerificationLevel VerificationLevel { get; private set; } public VerificationLevel VerificationLevel { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public MfaLevel MfaLevel { get; private set; } public MfaLevel MfaLevel { get; private set; }
@@ -41,8 +44,14 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public ulong? EmbedChannelId { get; private set; } public ulong? EmbedChannelId { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public ulong? WidgetChannelId { get; private set; }
/// <inheritdoc />
public ulong? SystemChannelId { get; private set; } public ulong? SystemChannelId { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public ulong? RulesChannelId { get; private set; }
/// <inheritdoc />
public ulong? PublicUpdatesChannelId { get; private set; }
/// <inheritdoc />
public ulong OwnerId { get; private set; } public ulong OwnerId { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public string VoiceRegionId { get; private set; } public string VoiceRegionId { get; private set; }
@@ -50,6 +59,8 @@ namespace Discord.Rest
public string IconId { get; private set; } public string IconId { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public string SplashId { get; private set; } public string SplashId { get; private set; }
/// <inheritdoc />
public string DiscoverySplashId { get; private set; }
internal bool Available { get; private set; } internal bool Available { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public ulong? ApplicationId { get; private set; } public ulong? ApplicationId { get; private set; }
@@ -67,6 +78,16 @@ namespace Discord.Rest
public int PremiumSubscriptionCount { get; private set; } public int PremiumSubscriptionCount { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public string PreferredLocale { get; private set; } public string PreferredLocale { get; private set; }
/// <inheritdoc />
public int? MaxPresences { get; private set; }
/// <inheritdoc />
public int? MaxMembers { get; private set; }
/// <inheritdoc />
public int? MaxVideoChannelUsers { get; private set; }
/// <inheritdoc />
public int? ApproximateMemberCount { get; private set; }
/// <inheritdoc />
public int? ApproximatePresenceCount { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
public CultureInfo PreferredCulture { get; private set; } public CultureInfo PreferredCulture { get; private set; }
@@ -81,6 +102,8 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId);
/// <inheritdoc /> /// <inheritdoc />
public string DiscoverySplashUrl => CDN.GetGuildDiscoverySplashUrl(Id, DiscoverySplashId);
/// <inheritdoc />
public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId); public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId);


/// <summary> /// <summary>
@@ -110,15 +133,24 @@ namespace Discord.Rest
internal void Update(Model model) internal void Update(Model model)
{ {
AFKChannelId = model.AFKChannelId; AFKChannelId = model.AFKChannelId;
EmbedChannelId = model.EmbedChannelId;
if (model.EmbedChannelId.IsSpecified)
EmbedChannelId = model.EmbedChannelId.Value;
if (model.WidgetChannelId.IsSpecified)
WidgetChannelId = model.WidgetChannelId.Value;
SystemChannelId = model.SystemChannelId; SystemChannelId = model.SystemChannelId;
RulesChannelId = model.RulesChannelId;
PublicUpdatesChannelId = model.PublicUpdatesChannelId;
AFKTimeout = model.AFKTimeout; AFKTimeout = model.AFKTimeout;
IsEmbeddable = model.EmbedEnabled;
if (model.EmbedEnabled.IsSpecified)
IsEmbeddable = model.EmbedEnabled.Value;
if (model.WidgetEnabled.IsSpecified)
IsWidgetEnabled = model.WidgetEnabled.Value;
IconId = model.Icon; IconId = model.Icon;
Name = model.Name; Name = model.Name;
OwnerId = model.OwnerId; OwnerId = model.OwnerId;
VoiceRegionId = model.Region; VoiceRegionId = model.Region;
SplashId = model.Splash; SplashId = model.Splash;
DiscoverySplashId = model.DiscoverySplash;
VerificationLevel = model.VerificationLevel; VerificationLevel = model.VerificationLevel;
MfaLevel = model.MfaLevel; MfaLevel = model.MfaLevel;
DefaultMessageNotifications = model.DefaultMessageNotifications; DefaultMessageNotifications = model.DefaultMessageNotifications;
@@ -130,8 +162,18 @@ namespace Discord.Rest
SystemChannelFlags = model.SystemChannelFlags; SystemChannelFlags = model.SystemChannelFlags;
Description = model.Description; Description = model.Description;
PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault(); PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault();
if (model.MaxPresences.IsSpecified)
MaxPresences = model.MaxPresences.Value ?? 25000;
if (model.MaxMembers.IsSpecified)
MaxMembers = model.MaxMembers.Value;
if (model.MaxVideoChannelUsers.IsSpecified)
MaxVideoChannelUsers = model.MaxVideoChannelUsers.Value;
PreferredLocale = model.PreferredLocale; PreferredLocale = model.PreferredLocale;
PreferredCulture = new CultureInfo(PreferredLocale); PreferredCulture = new CultureInfo(PreferredLocale);
if (model.ApproximateMemberCount.IsSpecified)
ApproximateMemberCount = model.ApproximateMemberCount.Value;
if (model.ApproximatePresenceCount.IsSpecified)
ApproximatePresenceCount = model.ApproximatePresenceCount.Value;


if (model.Emojis != null) if (model.Emojis != null)
{ {
@@ -163,17 +205,36 @@ namespace Discord.Rest
EmbedChannelId = model.ChannelId; EmbedChannelId = model.ChannelId;
IsEmbeddable = model.Enabled; IsEmbeddable = model.Enabled;
} }
internal void Update(WidgetModel model)
{
WidgetChannelId = model.ChannelId;
IsWidgetEnabled = model.Enabled;
}


//General //General
/// <inheritdoc /> /// <inheritdoc />
public async Task UpdateAsync(RequestOptions options = null) public async Task UpdateAsync(RequestOptions options = null)
=> Update(await Discord.ApiClient.GetGuildAsync(Id, options).ConfigureAwait(false));
=> Update(await Discord.ApiClient.GetGuildAsync(Id, false, options).ConfigureAwait(false));
/// <summary>
/// Updates this object's properties with its current state.
/// </summary>
/// <param name="withCounts">
/// If true, <see cref="ApproximateMemberCount"/> and <see cref="ApproximatePresenceCount"/>
/// will be updated as well.
/// </param>
/// <param name="options">The options to be used when sending the request.</param>
/// <remarks>
/// If <paramref name="withCounts"/> is true, <see cref="ApproximateMemberCount"/> and
/// <see cref="ApproximatePresenceCount"/> will be updated as well.
/// </remarks>
public async Task UpdateAsync(bool withCounts, RequestOptions options = null)
=> Update(await Discord.ApiClient.GetGuildAsync(Id, withCounts, options).ConfigureAwait(false));
/// <inheritdoc /> /// <inheritdoc />
public Task DeleteAsync(RequestOptions options = null) public Task DeleteAsync(RequestOptions options = null)
=> GuildHelper.DeleteAsync(this, Discord, options); => GuildHelper.DeleteAsync(this, Discord, options);


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public async Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null) public async Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null)
{ {
var model = await GuildHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); var model = await GuildHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false);
@@ -181,7 +242,8 @@ namespace Discord.Rest
} }


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")]
public async Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null) public async Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null)
{ {
var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false);
@@ -189,7 +251,15 @@ namespace Discord.Rest
} }


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="args" /> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public async Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null)
{
var model = await GuildHelper.ModifyWidgetAsync(this, Discord, func, options).ConfigureAwait(false);
Update(model);
}

/// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="args" /> is <see langword="null"/>.</exception>
public async Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null) public async Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null)
{ {
var arr = args.ToArray(); var arr = args.ToArray();
@@ -205,7 +275,7 @@ namespace Discord.Rest
role?.Update(model); role?.Update(model);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public Task LeaveAsync(RequestOptions options = null) public Task LeaveAsync(RequestOptions options = null)
=> GuildHelper.LeaveAsync(this, Discord, options); => GuildHelper.LeaveAsync(this, Discord, options);
@@ -230,7 +300,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains a ban object, which /// A task that represents the asynchronous get operation. The task result contains a ban object, which
/// contains the user information and the reason for the ban; <c>null</c> if the ban entry cannot be found.
/// contains the user information and the reason for the ban; <see langword="null"/> if the ban entry cannot be found.
/// </returns> /// </returns>
public Task<RestBan> GetBanAsync(IUser user, RequestOptions options = null) public Task<RestBan> GetBanAsync(IUser user, RequestOptions options = null)
=> GuildHelper.GetBanAsync(this, Discord, user.Id, options); => GuildHelper.GetBanAsync(this, Discord, user.Id, options);
@@ -241,7 +311,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains a ban object, which /// A task that represents the asynchronous get operation. The task result contains a ban object, which
/// contains the user information and the reason for the ban; <c>null</c> if the ban entry cannot be found.
/// contains the user information and the reason for the ban; <see langword="null"/> if the ban entry cannot be found.
/// </returns> /// </returns>
public Task<RestBan> GetBanAsync(ulong userId, RequestOptions options = null) public Task<RestBan> GetBanAsync(ulong userId, RequestOptions options = null)
=> GuildHelper.GetBanAsync(this, Discord, userId, options); => GuildHelper.GetBanAsync(this, Discord, userId, options);
@@ -279,7 +349,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the generic channel /// A task that represents the asynchronous get operation. The task result contains the generic channel
/// associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// associated with the specified <paramref name="id"/>; <see langword="null"/> if none is found.
/// </returns> /// </returns>
public Task<RestGuildChannel> GetChannelAsync(ulong id, RequestOptions options = null) public Task<RestGuildChannel> GetChannelAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetChannelAsync(this, Discord, id, options); => GuildHelper.GetChannelAsync(this, Discord, id, options);
@@ -291,7 +361,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel /// A task that represents the asynchronous get operation. The task result contains the text channel
/// associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// associated with the specified <paramref name="id"/>; <see langword="null"/> if none is found.
/// </returns> /// </returns>
public async Task<RestTextChannel> GetTextChannelAsync(ulong id, RequestOptions options = null) public async Task<RestTextChannel> GetTextChannelAsync(ulong id, RequestOptions options = null)
{ {
@@ -320,7 +390,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the voice channel associated /// A task that represents the asynchronous get operation. The task result contains the voice channel associated
/// with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// with the specified <paramref name="id"/>; <see langword="null"/> if none is found.
/// </returns> /// </returns>
public async Task<RestVoiceChannel> GetVoiceChannelAsync(ulong id, RequestOptions options = null) public async Task<RestVoiceChannel> GetVoiceChannelAsync(ulong id, RequestOptions options = null)
{ {
@@ -362,7 +432,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the voice channel that the /// A task that represents the asynchronous get operation. The task result contains the voice channel that the
/// AFK users will be moved to after they have idled for too long; <c>null</c> if none is set.
/// AFK users will be moved to after they have idled for too long; <see langword="null"/> if none is set.
/// </returns> /// </returns>
public async Task<RestVoiceChannel> GetAFKChannelAsync(RequestOptions options = null) public async Task<RestVoiceChannel> GetAFKChannelAsync(RequestOptions options = null)
{ {
@@ -381,7 +451,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the first viewable text /// A task that represents the asynchronous get operation. The task result contains the first viewable text
/// channel in this guild; <c>null</c> if none is found.
/// channel in this guild; <see langword="null"/> if none is found.
/// </returns> /// </returns>
public async Task<RestTextChannel> GetDefaultChannelAsync(RequestOptions options = null) public async Task<RestTextChannel> GetDefaultChannelAsync(RequestOptions options = null)
{ {
@@ -399,8 +469,9 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the embed channel set /// A task that represents the asynchronous get operation. The task result contains the embed channel set
/// within the server's widget settings; <c>null</c> if none is set.
/// within the server's widget settings; <see langword="null"/> if none is set.
/// </returns> /// </returns>
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")]
public async Task<RestGuildChannel> GetEmbedChannelAsync(RequestOptions options = null) public async Task<RestGuildChannel> GetEmbedChannelAsync(RequestOptions options = null)
{ {
var embedId = EmbedChannelId; var embedId = EmbedChannelId;
@@ -410,12 +481,28 @@ namespace Discord.Rest
} }


/// <summary> /// <summary>
/// Gets the first viewable text channel in this guild.
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary> /// </summary>
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the first viewable text
/// channel in this guild; <c>null</c> if none is found.
/// A task that represents the asynchronous get operation. The task result contains the widget channel set
/// within the server's widget settings; <see langword="null"/> if none is set.
/// </returns>
public async Task<RestGuildChannel> GetWidgetChannelAsync(RequestOptions options = null)
{
var widgetChannelId = WidgetChannelId;
if (widgetChannelId.HasValue)
return await GuildHelper.GetChannelAsync(this, Discord, widgetChannelId.Value, options).ConfigureAwait(false);
return null;
}

/// <summary>
/// Gets the text channel where guild notices such as welcome messages and boost events are posted.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel
/// where guild notices such as welcome messages and boost events are poste; <see langword="null"/> if none is found.
/// </returns> /// </returns>
public async Task<RestTextChannel> GetSystemChannelAsync(RequestOptions options = null) public async Task<RestTextChannel> GetSystemChannelAsync(RequestOptions options = null)
{ {
@@ -427,6 +514,45 @@ namespace Discord.Rest
} }
return null; return null;
} }

/// <summary>
/// Gets the text channel where Community guilds can display rules and/or guidelines.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel
/// where Community guilds can display rules and/or guidelines; <see langword="null"/> if none is set.
/// </returns>
public async Task<RestTextChannel> GetRulesChannelAsync(RequestOptions options = null)
{
var rulesChannelId = RulesChannelId;
if (rulesChannelId.HasValue)
{
var channel = await GuildHelper.GetChannelAsync(this, Discord, rulesChannelId.Value, options).ConfigureAwait(false);
return channel as RestTextChannel;
}
return null;
}

/// <summary>
/// Gets the text channel channel where admins and moderators of Community guilds receive notices from Discord.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the text channel channel where
/// admins and moderators of Community guilds receive notices from Discord; <see langword="null"/> if none is set.
/// </returns>
public async Task<RestTextChannel> GetPublicUpdatesChannelAsync(RequestOptions options = null)
{
var publicUpdatesChannelId = PublicUpdatesChannelId;
if (publicUpdatesChannelId.HasValue)
{
var channel = await GuildHelper.GetChannelAsync(this, Discord, publicUpdatesChannelId.Value, options).ConfigureAwait(false);
return channel as RestTextChannel;
}
return null;
}

/// <summary> /// <summary>
/// Creates a new text channel in this guild. /// Creates a new text channel in this guild.
/// </summary> /// </summary>
@@ -458,7 +584,7 @@ namespace Discord.Rest
/// <param name="name">The name of the new channel.</param> /// <param name="name">The name of the new channel.</param>
/// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param> /// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param>
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null"/>.</exception>
/// <returns> /// <returns>
/// The created voice channel. /// The created voice channel.
/// </returns> /// </returns>
@@ -470,7 +596,7 @@ namespace Discord.Rest
/// <param name="name">The name of the new channel.</param> /// <param name="name">The name of the new channel.</param>
/// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param> /// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param>
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null"/>.</exception>
/// <returns> /// <returns>
/// The created category channel. /// The created category channel.
/// </returns> /// </returns>
@@ -521,7 +647,7 @@ namespace Discord.Rest
/// </summary> /// </summary>
/// <param name="id">The snowflake identifier for the role.</param> /// <param name="id">The snowflake identifier for the role.</param>
/// <returns> /// <returns>
/// A role that is associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// A role that is associated with the specified <paramref name="id"/>; <see langword="null"/> if none is found.
/// </returns> /// </returns>
public RestRole GetRole(ulong id) public RestRole GetRole(ulong id)
{ {
@@ -585,7 +711,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the guild user /// A task that represents the asynchronous get operation. The task result contains the guild user
/// associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
/// associated with the specified <paramref name="id"/>; <see langword="null"/> if none is found.
/// </returns> /// </returns>
public Task<RestGuildUser> GetUserAsync(ulong id, RequestOptions options = null) public Task<RestGuildUser> GetUserAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetUserAsync(this, Discord, id, options); => GuildHelper.GetUserAsync(this, Discord, id, options);
@@ -631,8 +757,8 @@ namespace Discord.Rest
/// A task that represents the asynchronous prune operation. The task result contains the number of users to /// A task that represents the asynchronous prune operation. The task result contains the number of users to
/// be or has been removed from this guild. /// be or has been removed from this guild.
/// </returns> /// </returns>
public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null)
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options);
public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null, IEnumerable<ulong> includeRoleIds = null)
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options, includeRoleIds);


/// <summary> /// <summary>
/// Gets a collection of users in this guild that the name or nickname starts with the /// Gets a collection of users in this guild that the name or nickname starts with the
@@ -675,7 +801,7 @@ namespace Discord.Rest
/// <param name="options">The options to be used when sending the request.</param> /// <param name="options">The options to be used when sending the request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous get operation. The task result contains the webhook with the /// A task that represents the asynchronous get operation. The task result contains the webhook with the
/// specified <paramref name="id"/>; <c>null</c> if none is found.
/// specified <paramref name="id"/>; <see langword="null"/> if none is found.
/// </returns> /// </returns>
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetWebhookAsync(this, Discord, id, options); => GuildHelper.GetWebhookAsync(this, Discord, id, options);
@@ -708,7 +834,7 @@ namespace Discord.Rest
public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null) public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null)
=> GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options);
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null) public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null)
=> GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options);
/// <inheritdoc /> /// <inheritdoc />
@@ -808,6 +934,7 @@ namespace Discord.Rest
return null; return null;
} }
/// <inheritdoc /> /// <inheritdoc />
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")]
async Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) async Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -816,6 +943,14 @@ namespace Discord.Rest
return null; return null;
} }
/// <inheritdoc /> /// <inheritdoc />
async Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetWidgetChannelAsync(options).ConfigureAwait(false);
else
return null;
}
/// <inheritdoc />
async Task<ITextChannel> IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) async Task<ITextChannel> IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -824,6 +959,22 @@ namespace Discord.Rest
return null; return null;
} }
/// <inheritdoc /> /// <inheritdoc />
async Task<ITextChannel> IGuild.GetRulesChannelAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetRulesChannelAsync(options).ConfigureAwait(false);
else
return null;
}
/// <inheritdoc />
async Task<ITextChannel> IGuild.GetPublicUpdatesChannelAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetPublicUpdatesChannelAsync(options).ConfigureAwait(false);
else
return null;
}
/// <inheritdoc />
async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name, Action<TextChannelProperties> func, RequestOptions options) async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name, Action<TextChannelProperties> func, RequestOptions options)
=> await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />


+ 25
- 0
src/Discord.Net.Rest/Entities/Guilds/RestGuildWidget.cs View File

@@ -0,0 +1,25 @@
using System.Diagnostics;
using Model = Discord.API.GuildWidget;

namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public struct RestGuildWidget
{
public bool IsEnabled { get; private set; }
public ulong? ChannelId { get; private set; }

internal RestGuildWidget(bool isEnabled, ulong? channelId)
{
ChannelId = channelId;
IsEnabled = isEnabled;
}
internal static RestGuildWidget Create(Model model)
{
return new RestGuildWidget(model.Enabled, model.ChannelId);
}

public override string ToString() => ChannelId?.ToString() ?? "Unknown";
private string DebuggerDisplay => $"{ChannelId} ({(IsEnabled ? "Enabled" : "Disabled")})";
}
}

+ 2
- 0
src/Discord.Net.Rest/Entities/Messages/AllowedMentions.cs View File

@@ -11,5 +11,7 @@ namespace Discord.API
public Optional<ulong[]> Roles { get; set; } public Optional<ulong[]> Roles { get; set; }
[JsonProperty("users")] [JsonProperty("users")]
public Optional<ulong[]> Users { get; set; } public Optional<ulong[]> Users { get; set; }
[JsonProperty("replied_user")]
public Optional<bool> RepliedUser { get; set; }
} }
} }

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

@@ -3,7 +3,7 @@ using Model = Discord.API.Attachment;


namespace Discord namespace Discord
{ {
/// <inheritdoc/>
/// <inheritdoc cref="IAttachment"/>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class Attachment : IAttachment public class Attachment : IAttachment
{ {


+ 24
- 5
src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Message; using Model = Discord.API.Message;
using UserModel = Discord.API.User;


namespace Discord.Rest namespace Discord.Rest
{ {
@@ -65,12 +66,12 @@ namespace Discord.Rest


public static async Task AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) public static async Task AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options)
{ {
await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false);
await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false);
} }


public static async Task RemoveReactionAsync(IMessage msg, ulong userId, IEmote emote, BaseDiscordClient client, RequestOptions options) public static async Task RemoveReactionAsync(IMessage msg, ulong userId, IEmote emote, BaseDiscordClient client, RequestOptions options)
{ {
await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, userId, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false);
await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, userId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false);
} }


public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
@@ -80,14 +81,14 @@ namespace Discord.Rest


public static async Task RemoveAllReactionsForEmoteAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) public static async Task RemoveAllReactionsForEmoteAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options)
{ {
await client.ApiClient.RemoveAllReactionsForEmoteAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false);
await client.ApiClient.RemoveAllReactionsForEmoteAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false);
} }


public static IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IMessage msg, IEmote emote, public static IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IMessage msg, IEmote emote,
int? limit, BaseDiscordClient client, RequestOptions options) int? limit, BaseDiscordClient client, RequestOptions options)
{ {
Preconditions.NotNull(emote, nameof(emote)); Preconditions.NotNull(emote, nameof(emote));
var emoji = (emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name);
var emoji = (emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name));


return new PagedAsyncEnumerable<IUser>( return new PagedAsyncEnumerable<IUser>(
DiscordConfig.MaxUserReactionsPerBatch, DiscordConfig.MaxUserReactionsPerBatch,
@@ -114,7 +115,15 @@ namespace Discord.Rest
}, },
count: limit count: limit
); );
}


private static string UrlEncode(string text)
{
#if NET461
return System.Net.WebUtility.UrlEncode(text);
#else
return System.Web.HttpUtility.UrlEncode(text);
#endif
} }


public static async Task PinAsync(IMessage msg, BaseDiscordClient client, public static async Task PinAsync(IMessage msg, BaseDiscordClient client,
@@ -282,7 +291,7 @@ namespace Discord.Rest


public static MessageSource GetSource(Model msg) public static MessageSource GetSource(Model msg)
{ {
if (msg.Type != MessageType.Default)
if (msg.Type != MessageType.Default && msg.Type != MessageType.Reply)
return MessageSource.System; return MessageSource.System;
else if (msg.WebhookId.IsSpecified) else if (msg.WebhookId.IsSpecified)
return MessageSource.Webhook; return MessageSource.Webhook;
@@ -299,5 +308,15 @@ namespace Discord.Rest
{ {
await client.ApiClient.CrosspostAsync(channelId, msgId, options).ConfigureAwait(false); await client.ApiClient.CrosspostAsync(channelId, msgId, options).ConfigureAwait(false);
} }

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

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

@@ -37,6 +37,9 @@ namespace Discord.Rest
public virtual bool IsSuppressed => false; public virtual bool IsSuppressed => false;
/// <inheritdoc /> /// <inheritdoc />
public virtual DateTimeOffset? EditedTimestamp => null; public virtual DateTimeOffset? EditedTimestamp => null;
/// <inheritdoc />
public virtual bool MentionedEveryone => false;

/// <summary> /// <summary>
/// Gets a collection of the <see cref="Attachment"/>'s on the message. /// Gets a collection of the <see cref="Attachment"/>'s on the message.
/// </summary> /// </summary>
@@ -74,7 +77,7 @@ namespace Discord.Rest
} }
internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model)
{ {
if (model.Type == MessageType.Default)
if (model.Type == MessageType.Default || model.Type == MessageType.Reply)
return RestUserMessage.Create(discord, channel, author, model); return RestUserMessage.Create(discord, channel, author, model);
else else
return RestSystemMessage.Create(discord, channel, author, model); return RestSystemMessage.Create(discord, channel, author, model);
@@ -116,7 +119,7 @@ namespace Discord.Rest
Reference = new MessageReference Reference = new MessageReference
{ {
GuildId = model.Reference.Value.GuildId, GuildId = model.Reference.Value.GuildId,
ChannelId = model.Reference.Value.ChannelId,
InternalChannelId = model.Reference.Value.ChannelId,
MessageId = model.Reference.Value.MessageId MessageId = model.Reference.Value.MessageId
}; };
} }


+ 25
- 10
src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs View File

@@ -15,9 +15,12 @@ namespace Discord.Rest
{ {
private bool _isMentioningEveryone, _isTTS, _isPinned, _isSuppressed; private bool _isMentioningEveryone, _isTTS, _isPinned, _isSuppressed;
private long? _editedTimestampTicks; private long? _editedTimestampTicks;
private IUserMessage _referencedMessage;
private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>();
private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>(); private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>();
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>(); private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>();
private ImmutableArray<ulong> _roleMentionIds = ImmutableArray.Create<ulong>();
private ImmutableArray<RestUser> _userMentions = ImmutableArray.Create<RestUser>();


/// <inheritdoc /> /// <inheritdoc />
public override bool IsTTS => _isTTS; public override bool IsTTS => _isTTS;
@@ -28,17 +31,21 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks);
/// <inheritdoc /> /// <inheritdoc />
public override bool MentionedEveryone => _isMentioningEveryone;
/// <inheritdoc />
public override IReadOnlyCollection<Attachment> Attachments => _attachments; public override IReadOnlyCollection<Attachment> Attachments => _attachments;
/// <inheritdoc /> /// <inheritdoc />
public override IReadOnlyCollection<Embed> Embeds => _embeds; public override IReadOnlyCollection<Embed> Embeds => _embeds;
/// <inheritdoc /> /// <inheritdoc />
public override IReadOnlyCollection<ulong> MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); public override IReadOnlyCollection<ulong> MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags);
/// <inheritdoc /> /// <inheritdoc />
public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags);
public override IReadOnlyCollection<ulong> MentionedRoleIds => _roleMentionIds;
/// <inheritdoc /> /// <inheritdoc />
public override IReadOnlyCollection<RestUser> MentionedUsers => MessageHelper.FilterTagsByValue<RestUser>(TagType.UserMention, _tags);
public override IReadOnlyCollection<RestUser> MentionedUsers => _userMentions;
/// <inheritdoc /> /// <inheritdoc />
public override IReadOnlyCollection<ITag> Tags => _tags; public override IReadOnlyCollection<ITag> Tags => _tags;
/// <inheritdoc />
public IUserMessage ReferencedMessage => _referencedMessage;


internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source)
: base(discord, id, channel, author, source) : base(discord, id, channel, author, source)
@@ -67,6 +74,8 @@ namespace Discord.Rest
{ {
_isSuppressed = model.Flags.Value.HasFlag(API.MessageFlags.Suppressed); _isSuppressed = model.Flags.Value.HasFlag(API.MessageFlags.Suppressed);
} }
if (model.RoleMentions.IsSpecified)
_roleMentionIds = model.RoleMentions.Value.ToImmutableArray();


if (model.Attachments.IsSpecified) if (model.Attachments.IsSpecified)
{ {
@@ -96,31 +105,37 @@ namespace Discord.Rest
_embeds = ImmutableArray.Create<Embed>(); _embeds = ImmutableArray.Create<Embed>();
} }


ImmutableArray<IUser> mentions = ImmutableArray.Create<IUser>();
if (model.UserMentions.IsSpecified) if (model.UserMentions.IsSpecified)
{ {
var value = model.UserMentions.Value; var value = model.UserMentions.Value;
if (value.Length > 0) if (value.Length > 0)
{ {
var newMentions = ImmutableArray.CreateBuilder<IUser>(value.Length);
var newMentions = ImmutableArray.CreateBuilder<RestUser>(value.Length);
for (int i = 0; i < value.Length; i++) for (int i = 0; i < value.Length; i++)
{ {
var val = value[i]; var val = value[i];
if (val.Object != null) if (val.Object != null)
newMentions.Add(RestUser.Create(Discord, val.Object)); newMentions.Add(RestUser.Create(Discord, val.Object));
} }
mentions = newMentions.ToImmutable();
_userMentions = newMentions.ToImmutable();
} }
} }


var guildId = (Channel as IGuildChannel)?.GuildId;
var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null;
if (model.Content.IsSpecified) if (model.Content.IsSpecified)
{ {
var text = model.Content.Value; var text = model.Content.Value;
var guildId = (Channel as IGuildChannel)?.GuildId;
var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null;
_tags = MessageHelper.ParseTags(text, null, guild, mentions);
_tags = MessageHelper.ParseTags(text, null, guild, _userMentions);
model.Content = text; model.Content = text;
} }

if (model.ReferencedMessage.IsSpecified && model.ReferencedMessage.Value != null)
{
var refMsg = model.ReferencedMessage.Value;
IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable());
_referencedMessage = RestUserMessage.Create(Discord, Channel, refMsgAuthor, refMsg);
}
} }


/// <inheritdoc /> /// <inheritdoc />
@@ -149,10 +164,10 @@ namespace Discord.Rest
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="InvalidOperationException">This operation may only be called on a <see cref="RestNewsChannel"/> channel.</exception>
/// <exception cref="InvalidOperationException">This operation may only be called on a <see cref="INewsChannel"/> channel.</exception>
public async Task CrosspostAsync(RequestOptions options = null) public async Task CrosspostAsync(RequestOptions options = null)
{ {
if (!(Channel is RestNewsChannel))
if (!(Channel is INewsChannel))
{ {
throw new InvalidOperationException("Publishing (crossposting) is only valid in news channels."); throw new InvalidOperationException("Publishing (crossposting) is only valid in news channels.");
} }


+ 10
- 0
src/Discord.Net.Rest/Entities/RestApplication.cs View File

@@ -21,6 +21,12 @@ namespace Discord.Rest
public string[] RPCOrigins { get; private set; } public string[] RPCOrigins { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public ulong Flags { get; private set; } public ulong Flags { get; private set; }
/// <inheritdoc />
public bool IsBotPublic { get; private set; }
/// <inheritdoc />
public bool BotRequiresCodeGrant { get; private set; }
/// <inheritdoc />
public ITeam Team { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
public IUser Owner { get; private set; } public IUser Owner { get; private set; }
@@ -46,11 +52,15 @@ namespace Discord.Rest
RPCOrigins = model.RPCOrigins; RPCOrigins = model.RPCOrigins;
Name = model.Name; Name = model.Name;
_iconId = model.Icon; _iconId = model.Icon;
IsBotPublic = model.IsBotPublic;
BotRequiresCodeGrant = model.BotRequiresCodeGrant;


if (model.Flags.IsSpecified) if (model.Flags.IsSpecified)
Flags = model.Flags.Value; //TODO: Do we still need this? Flags = model.Flags.Value; //TODO: Do we still need this?
if (model.Owner.IsSpecified) if (model.Owner.IsSpecified)
Owner = RestUser.Create(Discord, model.Owner.Value); Owner = RestUser.Create(Discord, model.Owner.Value);
if (model.Team != null)
Team = RestTeam.Create(Discord, model.Team);
} }


/// <exception cref="InvalidOperationException">Unable to update this object from a different application token.</exception> /// <exception cref="InvalidOperationException">Unable to update this object from a different application token.</exception>


+ 37
- 0
src/Discord.Net.Rest/Entities/Teams/RestTeam.cs View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Model = Discord.API.Team;

namespace Discord.Rest
{
public class RestTeam : RestEntity<ulong>, ITeam
{
/// <inheritdoc />
public string IconUrl => _iconId != null ? CDN.GetTeamIconUrl(Id, _iconId) : null;
/// <inheritdoc />
public IReadOnlyList<ITeamMember> TeamMembers { get; private set; }
/// <inheritdoc />
public ulong OwnerUserId { get; private set; }

private string _iconId;

internal RestTeam(BaseDiscordClient discord, ulong id)
: base(discord, id)
{
}
internal static RestTeam Create(BaseDiscordClient discord, Model model)
{
var entity = new RestTeam(discord, model.Id);
entity.Update(model);
return entity;
}
internal virtual void Update(Model model)
{
if (model.Icon.IsSpecified)
_iconId = model.Icon.Value;
OwnerUserId = model.OwnerUserId;
TeamMembers = model.TeamMembers.Select(x => new RestTeamMember(Discord, x)).ToImmutableArray();
}
}
}

+ 30
- 0
src/Discord.Net.Rest/Entities/Teams/RestTeamMember.cs View File

@@ -0,0 +1,30 @@
using System;
using Model = Discord.API.TeamMember;

namespace Discord.Rest
{
public class RestTeamMember : ITeamMember
{
/// <inheritdoc />
public MembershipState MembershipState { get; }
/// <inheritdoc />
public string[] Permissions { get; }
/// <inheritdoc />
public ulong TeamId { get; }
/// <inheritdoc />
public IUser User { get; }

internal RestTeamMember(BaseDiscordClient discord, Model model)
{
MembershipState = model.MembershipState switch
{
API.MembershipState.Invited => MembershipState.Invited,
API.MembershipState.Accepted => MembershipState.Accepted,
_ => throw new InvalidOperationException("Invalid membership state"),
};
Permissions = model.Permissions;
TeamId = model.TeamId;
User = RestUser.Create(discord, model.User);
}
}
}

+ 10
- 0
src/Discord.Net.Rest/Extensions/EntityExtensions.cs View File

@@ -68,6 +68,16 @@ namespace Discord.Rest
Parse = entity.AllowedTypes?.EnumerateMentionTypes().ToArray(), Parse = entity.AllowedTypes?.EnumerateMentionTypes().ToArray(),
Roles = entity.RoleIds?.ToArray(), Roles = entity.RoleIds?.ToArray(),
Users = entity.UserIds?.ToArray(), Users = entity.UserIds?.ToArray(),
RepliedUser = entity.MentionRepliedUser ?? Optional.Create<bool>(),
};
}
public static API.MessageReference ToModel(this MessageReference entity)
{
return new API.MessageReference()
{
ChannelId = entity.InternalChannelId,
GuildId = entity.GuildId,
MessageId = entity.MessageId,
}; };
} }
public static IEnumerable<string> EnumerateMentionTypes(this AllowedMentionTypes mentionTypes) public static IEnumerable<string> EnumerateMentionTypes(this AllowedMentionTypes mentionTypes)


+ 53
- 0
src/Discord.Net.Rest/Net/Queue/GatewayBucket.cs View File

@@ -0,0 +1,53 @@
using System.Collections.Immutable;

namespace Discord.Net.Queue
{
public enum GatewayBucketType
{
Unbucketed = 0,
Identify = 1,
PresenceUpdate = 2,
}
internal struct GatewayBucket
{
private static readonly ImmutableDictionary<GatewayBucketType, GatewayBucket> DefsByType;
private static readonly ImmutableDictionary<BucketId, GatewayBucket> DefsById;

static GatewayBucket()
{
var buckets = new[]
{
// Limit is 120/60s, but 3 will be reserved for heartbeats (2 for possible heartbeats in the same timeframe and a possible failure)
new GatewayBucket(GatewayBucketType.Unbucketed, BucketId.Create(null, "<gateway-unbucketed>", null), 117, 60),
new GatewayBucket(GatewayBucketType.Identify, BucketId.Create(null, "<gateway-identify>", null), 1, 5),
new GatewayBucket(GatewayBucketType.PresenceUpdate, BucketId.Create(null, "<gateway-presenceupdate>", null), 5, 60),
};

var builder = ImmutableDictionary.CreateBuilder<GatewayBucketType, GatewayBucket>();
foreach (var bucket in buckets)
builder.Add(bucket.Type, bucket);
DefsByType = builder.ToImmutable();

var builder2 = ImmutableDictionary.CreateBuilder<BucketId, GatewayBucket>();
foreach (var bucket in buckets)
builder2.Add(bucket.Id, bucket);
DefsById = builder2.ToImmutable();
}

public static GatewayBucket Get(GatewayBucketType type) => DefsByType[type];
public static GatewayBucket Get(BucketId id) => DefsById[id];

public GatewayBucketType Type { get; }
public BucketId Id { get; }
public int WindowCount { get; set; }
public int WindowSeconds { get; set; }

public GatewayBucket(GatewayBucketType type, BucketId id, int count, int seconds)
{
Type = type;
Id = id;
WindowCount = count;
WindowSeconds = seconds;
}
}
}

+ 37
- 7
src/Discord.Net.Rest/Net/Queue/RequestQueue.cs View File

@@ -12,7 +12,7 @@ namespace Discord.Net.Queue
{ {
internal class RequestQueue : IDisposable internal class RequestQueue : IDisposable
{ {
public event Func<BucketId, RateLimitInfo?, Task> RateLimitTriggered;
public event Func<BucketId, RateLimitInfo?, string, Task> RateLimitTriggered;


private readonly ConcurrentDictionary<BucketId, object> _buckets; private readonly ConcurrentDictionary<BucketId, object> _buckets;
private readonly SemaphoreSlim _tokenLock; private readonly SemaphoreSlim _tokenLock;
@@ -89,9 +89,18 @@ namespace Discord.Net.Queue
} }
public async Task SendAsync(WebSocketRequest request) public async Task SendAsync(WebSocketRequest request)
{ {
//TODO: Re-impl websocket buckets
request.CancelToken = _requestCancelToken;
await request.SendAsync().ConfigureAwait(false);
CancellationTokenSource createdTokenSource = null;
if (request.Options.CancelToken.CanBeCanceled)
{
createdTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_requestCancelToken, request.Options.CancelToken);
request.Options.CancelToken = createdTokenSource.Token;
}
else
request.Options.CancelToken = _requestCancelToken;

var bucket = GetOrCreateBucket(request.Options, request);
await bucket.SendAsync(request).ConfigureAwait(false);
createdTokenSource?.Dispose();
} }


internal async Task EnterGlobalAsync(int id, RestRequest request) internal async Task EnterGlobalAsync(int id, RestRequest request)
@@ -109,8 +118,23 @@ namespace Discord.Net.Queue
{ {
_waitUntil = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value + (info.Lag?.TotalMilliseconds ?? 0.0)); _waitUntil = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value + (info.Lag?.TotalMilliseconds ?? 0.0));
} }
internal async Task EnterGlobalAsync(int id, WebSocketRequest request)
{
//If this is a global request (unbucketed), it'll be dealt in EnterAsync
var requestBucket = GatewayBucket.Get(request.Options.BucketId);
if (requestBucket.Type == GatewayBucketType.Unbucketed)
return;

//It's not a global request, so need to remove one from global (per-session)
var globalBucketType = GatewayBucket.Get(GatewayBucketType.Unbucketed);
var options = RequestOptions.CreateOrClone(request.Options);
options.BucketId = globalBucketType.Id;
var globalRequest = new WebSocketRequest(null, null, false, false, options);
var globalBucket = GetOrCreateBucket(options, globalRequest);
await globalBucket.TriggerAsync(id, globalRequest);
}


private RequestBucket GetOrCreateBucket(RequestOptions options, RestRequest request)
private RequestBucket GetOrCreateBucket(RequestOptions options, IRequest request)
{ {
var bucketId = options.BucketId; var bucketId = options.BucketId;
object obj = _buckets.GetOrAdd(bucketId, x => new RequestBucket(this, request, x)); object obj = _buckets.GetOrAdd(bucketId, x => new RequestBucket(this, request, x));
@@ -121,9 +145,9 @@ namespace Discord.Net.Queue
} }
return (RequestBucket)obj; return (RequestBucket)obj;
} }
internal async Task RaiseRateLimitTriggered(BucketId bucketId, RateLimitInfo? info)
internal async Task RaiseRateLimitTriggered(BucketId bucketId, RateLimitInfo? info, string endpoint)
{ {
await RateLimitTriggered(bucketId, info).ConfigureAwait(false);
await RateLimitTriggered(bucketId, info, endpoint).ConfigureAwait(false);
} }
internal (RequestBucket, BucketId) UpdateBucketHash(BucketId id, string discordHash) internal (RequestBucket, BucketId) UpdateBucketHash(BucketId id, string discordHash)
{ {
@@ -137,6 +161,12 @@ namespace Discord.Net.Queue
return (null, null); return (null, null);
} }


public void ClearGatewayBuckets()
{
foreach (var gwBucket in (GatewayBucketType[])Enum.GetValues(typeof(GatewayBucketType)))
_buckets.TryRemove(GatewayBucket.Get(gwBucket).Id, out _);
}

private async Task RunCleanup() private async Task RunCleanup()
{ {
try try


+ 111
- 9
src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs View File

@@ -25,7 +25,7 @@ namespace Discord.Net.Queue
public int WindowCount { get; private set; } public int WindowCount { get; private set; }
public DateTimeOffset LastAttemptAt { get; private set; } public DateTimeOffset LastAttemptAt { get; private set; }


public RequestBucket(RequestQueue queue, RestRequest request, BucketId id)
public RequestBucket(RequestQueue queue, IRequest request, BucketId id)
{ {
_queue = queue; _queue = queue;
Id = id; Id = id;
@@ -33,7 +33,9 @@ namespace Discord.Net.Queue
_lock = new object(); _lock = new object();


if (request.Options.IsClientBucket) if (request.Options.IsClientBucket)
WindowCount = ClientBucket.Get(Id).WindowCount;
WindowCount = ClientBucket.Get(request.Options.BucketId).WindowCount;
else if (request.Options.IsGatewayBucket)
WindowCount = GatewayBucket.Get(request.Options.BucketId).WindowCount;
else else
WindowCount = 1; //Only allow one request until we get a header back WindowCount = 1; //Only allow one request until we get a header back
_semaphore = WindowCount; _semaphore = WindowCount;
@@ -84,7 +86,7 @@ namespace Discord.Net.Queue
#endif #endif
UpdateRateLimit(id, request, info, true); UpdateRateLimit(id, request, info, true);
} }
await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false);
await _queue.RaiseRateLimitTriggered(Id, info, $"{request.Method} {request.Endpoint}").ConfigureAwait(false);
continue; //Retry continue; //Retry
case HttpStatusCode.BadGateway: //502 case HttpStatusCode.BadGateway: //502
#if DEBUG_LIMITS #if DEBUG_LIMITS
@@ -154,8 +156,68 @@ namespace Discord.Net.Queue
} }
} }
} }
public async Task SendAsync(WebSocketRequest request)
{
int id = Interlocked.Increment(ref nextId);
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Start");
#endif
LastAttemptAt = DateTimeOffset.UtcNow;
while (true)
{
await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false);
await EnterAsync(id, request).ConfigureAwait(false);


private async Task EnterAsync(int id, RestRequest request)
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Sending...");
#endif
try
{
await request.SendAsync().ConfigureAwait(false);
return;
}
catch (TimeoutException)
{
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Timeout");
#endif
if ((request.Options.RetryMode & RetryMode.RetryTimeouts) == 0)
throw;

await Task.Delay(500).ConfigureAwait(false);
continue; //Retry
}
/*catch (Exception)
{
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Error");
#endif
if ((request.Options.RetryMode & RetryMode.RetryErrors) == 0)
throw;

await Task.Delay(500);
continue; //Retry
}*/
finally
{
UpdateRateLimit(id, request, default(RateLimitInfo), false);
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Stop");
#endif
}
}
}

internal async Task TriggerAsync(int id, IRequest request)
{
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Trigger Bucket");
#endif
await EnterAsync(id, request).ConfigureAwait(false);
UpdateRateLimit(id, request, default(RateLimitInfo), false);
}

private async Task EnterAsync(int id, IRequest request)
{ {
int windowCount; int windowCount;
DateTimeOffset? resetAt; DateTimeOffset? resetAt;
@@ -186,8 +248,31 @@ namespace Discord.Net.Queue
{ {
if (!isRateLimited) if (!isRateLimited)
{ {
bool ignoreRatelimit = false;
isRateLimited = true; isRateLimited = true;
await _queue.RaiseRateLimitTriggered(Id, null).ConfigureAwait(false);
switch (request)
{
case RestRequest restRequest:
await _queue.RaiseRateLimitTriggered(Id, null, $"{restRequest.Method} {restRequest.Endpoint}").ConfigureAwait(false);
break;
case WebSocketRequest webSocketRequest:
if (webSocketRequest.IgnoreLimit)
{
ignoreRatelimit = true;
break;
}
await _queue.RaiseRateLimitTriggered(Id, null, Id.Endpoint).ConfigureAwait(false);
break;
default:
throw new InvalidOperationException("Unknown request type");
}
if (ignoreRatelimit)
{
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Ignoring ratelimit");
#endif
break;
}
} }


ThrowRetryLimit(request); ThrowRetryLimit(request);
@@ -223,7 +308,7 @@ namespace Discord.Net.Queue
} }
} }


private void UpdateRateLimit(int id, RestRequest request, RateLimitInfo info, bool is429, bool redirected = false)
private void UpdateRateLimit(int id, IRequest request, RateLimitInfo info, bool is429, bool redirected = false)
{ {
if (WindowCount == 0) if (WindowCount == 0)
return; return;
@@ -316,6 +401,23 @@ namespace Discord.Net.Queue
Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(Id).WindowSeconds * 1000} ms)"); Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(Id).WindowSeconds * 1000} ms)");
#endif #endif
} }
else if (request.Options.IsGatewayBucket && request.Options.BucketId != null)
{
resetTick = DateTimeOffset.UtcNow.AddSeconds(GatewayBucket.Get(request.Options.BucketId).WindowSeconds);
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Gateway Bucket ({GatewayBucket.Get(request.Options.BucketId).WindowSeconds * 1000} ms)");
#endif
if (!hasQueuedReset)
{
_resetTick = resetTick;
LastAttemptAt = resetTick.Value;
#if DEBUG_LIMITS
Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms");
#endif
var _ = QueueReset(id, (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds), request);
}
return;
}


if (resetTick == null) if (resetTick == null)
{ {
@@ -336,12 +438,12 @@ namespace Discord.Net.Queue


if (!hasQueuedReset) if (!hasQueuedReset)
{ {
var _ = QueueReset(id, (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds));
var _ = QueueReset(id, (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds), request);
} }
} }
} }
} }
private async Task QueueReset(int id, int millis)
private async Task QueueReset(int id, int millis, IRequest request)
{ {
while (true) while (true)
{ {
@@ -363,7 +465,7 @@ namespace Discord.Net.Queue
} }
} }


private void ThrowRetryLimit(RestRequest request)
private void ThrowRetryLimit(IRequest request)
{ {
if ((request.Options.RetryMode & RetryMode.RetryRatelimit) == 0) if ((request.Options.RetryMode & RetryMode.RetryRatelimit) == 0)
throw new RateLimitedException(request); throw new RateLimitedException(request);


+ 3
- 3
src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs View File

@@ -9,22 +9,22 @@ namespace Discord.Net.Queue
public class WebSocketRequest : IRequest public class WebSocketRequest : IRequest
{ {
public IWebSocketClient Client { get; } public IWebSocketClient Client { get; }
public string BucketId { get; }
public byte[] Data { get; } public byte[] Data { get; }
public bool IsText { get; } public bool IsText { get; }
public bool IgnoreLimit { get; }
public DateTimeOffset? TimeoutAt { get; } public DateTimeOffset? TimeoutAt { get; }
public TaskCompletionSource<Stream> Promise { get; } public TaskCompletionSource<Stream> Promise { get; }
public RequestOptions Options { get; } public RequestOptions Options { get; }
public CancellationToken CancelToken { get; internal set; } public CancellationToken CancelToken { get; internal set; }


public WebSocketRequest(IWebSocketClient client, string bucketId, byte[] data, bool isText, RequestOptions options)
public WebSocketRequest(IWebSocketClient client, byte[] data, bool isText, bool ignoreLimit, RequestOptions options)
{ {
Preconditions.NotNull(options, nameof(options)); Preconditions.NotNull(options, nameof(options));


Client = client; Client = client;
BucketId = bucketId;
Data = data; Data = data;
IsText = isText; IsText = isText;
IgnoreLimit = ignoreLimit;
Options = options; Options = options;
TimeoutAt = options.Timeout.HasValue ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) : (DateTimeOffset?)null; TimeoutAt = options.Timeout.HasValue ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) : (DateTimeOffset?)null;
Promise = new TaskCompletionSource<Stream>(); Promise = new TaskCompletionSource<Stream>();


+ 2
- 0
src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs View File

@@ -15,6 +15,8 @@ namespace Discord.API.Gateway
public int LargeThreshold { get; set; } public int LargeThreshold { get; set; }
[JsonProperty("shard")] [JsonProperty("shard")]
public Optional<int[]> ShardingParams { get; set; } public Optional<int[]> ShardingParams { get; set; }
[JsonProperty("presence")]
public Optional<StatusUpdateParams> Presence { get; set; }
[JsonProperty("guild_subscriptions")] [JsonProperty("guild_subscriptions")]
public Optional<bool> GuildSubscriptions { get; set; } public Optional<bool> GuildSubscriptions { get; set; }
[JsonProperty("intents")] [JsonProperty("intents")]


+ 31
- 0
src/Discord.Net.WebSocket/API/Gateway/InviteCreateEvent.cs View File

@@ -0,0 +1,31 @@
using Newtonsoft.Json;
using System;

namespace Discord.API.Gateway
{
internal class InviteCreateEvent
{
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("created_at")]
public DateTimeOffset CreatedAt { get; set; }
[JsonProperty("guild_id")]
public Optional<ulong> GuildId { get; set; }
[JsonProperty("inviter")]
public Optional<User> Inviter { get; set; }
[JsonProperty("max_age")]
public int MaxAge { get; set; }
[JsonProperty("max_uses")]
public int MaxUses { get; set; }
[JsonProperty("target_user")]
public Optional<User> TargetUser { get; set; }
[JsonProperty("target_user_type")]
public Optional<TargetUserType> TargetUserType { get; set; }
[JsonProperty("temporary")]
public bool Temporary { get; set; }
[JsonProperty("uses")]
public int Uses { get; set; }
}
}

+ 14
- 0
src/Discord.Net.WebSocket/API/Gateway/InviteDeleteEvent.cs View File

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

namespace Discord.API.Gateway
{
internal class InviteDeleteEvent
{
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("guild_id")]
public Optional<ulong> GuildId { get; set; }
}
}

+ 1
- 1
src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs View File

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


namespace Discord.API.Gateway namespace Discord.API.Gateway


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

Loading…
Cancel
Save