Browse Source

Add XML docs

pull/988/head
Still Hsu 7 years ago
parent
commit
5092358401
No known key found for this signature in database GPG Key ID: 8601A145FDA95209
89 changed files with 920 additions and 183 deletions
  1. +1
    -1
      docs/guides/getting_started/samples/first-bot/structure.cs
  2. +1
    -1
      experiment/Discord.Net.Rpc/DiscordRpcApiClient.cs
  3. +2
    -2
      experiment/Discord.Net.Rpc/DiscordRpcConfig.cs
  4. +1
    -0
      src/Discord.Net.Commands/Builders/CommandBuilder.cs
  5. +9
    -9
      src/Discord.Net.Commands/CommandService.cs
  6. +1
    -1
      src/Discord.Net.Commands/Info/CommandInfo.cs
  7. +1
    -0
      src/Discord.Net.Commands/Map/CommandMapNode.cs
  8. +2
    -0
      src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs
  9. +1
    -1
      src/Discord.Net.Core/Audio/IAudioClient.cs
  10. +2
    -2
      src/Discord.Net.Core/CDN.cs
  11. +1
    -1
      src/Discord.Net.Core/Commands/ICommandContext.cs
  12. +1
    -1
      src/Discord.Net.Core/Entities/Activities/Game.cs
  13. +1
    -1
      src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs
  14. +2
    -2
      src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs
  15. +154
    -18
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  16. +1
    -1
      src/Discord.Net.Core/Entities/IUpdateable.cs
  17. +2
    -2
      src/Discord.Net.Core/Entities/Image.cs
  18. +1
    -1
      src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
  19. +1
    -1
      src/Discord.Net.Core/Entities/Messages/EmbedField.cs
  20. +1
    -1
      src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs
  21. +1
    -1
      src/Discord.Net.Core/Entities/Messages/IMessage.cs
  22. +13
    -12
      src/Discord.Net.Core/Entities/Messages/MessageProperties.cs
  23. +2
    -1
      src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
  24. +1
    -1
      src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs
  25. +1
    -1
      src/Discord.Net.Core/Entities/Roles/RoleProperties.cs
  26. +5
    -5
      src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs
  27. +2
    -2
      src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs
  28. +13
    -7
      src/Discord.Net.Core/Utils/ConcurrentHashSet.cs
  29. +1
    -0
      src/Discord.Net.Core/Utils/Optional.cs
  30. +2
    -2
      src/Discord.Net.Core/Utils/Preconditions.cs
  31. +2
    -0
      src/Discord.Net.Rest/ClientHelper.cs
  32. +16
    -3
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  33. +16
    -0
      src/Discord.Net.Rest/DiscordRestClient.cs
  34. +29
    -9
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  35. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs
  36. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/IRestPrivateChannel.cs
  37. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs
  38. +8
    -1
      src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
  39. +18
    -2
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  40. +13
    -0
      src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
  41. +12
    -5
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  42. +3
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestBan.cs
  43. +82
    -4
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  44. +2
    -2
      src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs
  45. +13
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs
  46. +7
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs
  47. +3
    -1
      src/Discord.Net.Rest/Entities/Invites/RestInvite.cs
  48. +3
    -1
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  49. +5
    -0
      src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
  50. +21
    -4
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  51. +8
    -0
      src/Discord.Net.Rest/Entities/RestApplication.cs
  52. +14
    -1
      src/Discord.Net.Rest/Entities/Roles/RestRole.cs
  53. +12
    -1
      src/Discord.Net.Rest/Entities/Users/RestConnection.cs
  54. +9
    -0
      src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
  55. +9
    -1
      src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs
  56. +14
    -0
      src/Discord.Net.Rest/Entities/Users/RestUser.cs
  57. +14
    -1
      src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
  58. +3
    -1
      src/Discord.Net.Rest/Net/DefaultRestClient.cs
  59. +1
    -0
      src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs
  60. +1
    -1
      src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs
  61. +2
    -2
      src/Discord.Net.Rest/Net/RateLimitInfo.cs
  62. +6
    -3
      src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
  63. +4
    -1
      src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs
  64. +7
    -3
      src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs
  65. +1
    -1
      src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
  66. +137
    -5
      src/Discord.Net.WebSocket/BaseSocketClient.cs
  67. +3
    -3
      src/Discord.Net.WebSocket/ClientState.cs
  68. +3
    -1
      src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs
  69. +6
    -3
      src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
  70. +37
    -7
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  71. +1
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs
  72. +5
    -4
      src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs
  73. +0
    -1
      src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
  74. +14
    -2
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  75. +21
    -1
      src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
  76. +4
    -1
      src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs
  77. +2
    -1
      src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs
  78. +24
    -4
      src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
  79. +14
    -1
      src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs
  80. +1
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
  81. +3
    -2
      src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs
  82. +12
    -2
      src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
  83. +3
    -2
      src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
  84. +12
    -2
      src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
  85. +17
    -3
      src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs
  86. +6
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
  87. +2
    -2
      src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs
  88. +2
    -1
      src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs
  89. +3
    -2
      src/Discord.Net.Webhook/WebhookClientHelper.cs

+ 1
- 1
docs/guides/getting_started/samples/first-bot/structure.cs View File

@@ -36,7 +36,7 @@ class Program
// you must set the MessageCacheSize. You may adjust the number as needed. // you must set the MessageCacheSize. You may adjust the number as needed.
//MessageCacheSize = 50, //MessageCacheSize = 50,


// If your platform doesn't have native websockets,
// If your platform doesn't have native WebSockets,
// add Discord.Net.Providers.WS4Net from NuGet, // add Discord.Net.Providers.WS4Net from NuGet,
// add the `using` at the top, and uncomment this line: // add the `using` at the top, and uncomment this line:
//WebSocketProvider = WS4NetProvider.Instance //WebSocketProvider = WS4NetProvider.Instance


+ 1
- 1
experiment/Discord.Net.Rpc/DiscordRpcApiClient.cs View File

@@ -192,7 +192,7 @@ namespace Discord.API
internal override async Task DisconnectInternalAsync() internal override async Task DisconnectInternalAsync()
{ {
if (_webSocketClient == null) if (_webSocketClient == null)
throw new NotSupportedException("This client is not configured with websocket support.");
throw new NotSupportedException("This client is not configured with WebSocket support.");


if (ConnectionState == ConnectionState.Disconnected) return; if (ConnectionState == ConnectionState.Disconnected) return;
ConnectionState = ConnectionState.Disconnecting; ConnectionState = ConnectionState.Disconnecting;


+ 2
- 2
experiment/Discord.Net.Rpc/DiscordRpcConfig.cs View File

@@ -14,7 +14,7 @@ namespace Discord.Rpc
/// <summary> Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. </summary> /// <summary> Gets or sets the time, in milliseconds, to wait for a connection to complete before aborting. </summary>
public int ConnectionTimeout { get; set; } = 30000; public int ConnectionTimeout { get; set; } = 30000;


/// <summary> Gets or sets the provider used to generate new websocket connections. </summary>
/// <summary> Gets or sets the provider used to generate new WebSocket connections. </summary>
public WebSocketProvider WebSocketProvider { get; set; } public WebSocketProvider WebSocketProvider { get; set; }


public DiscordRpcConfig() public DiscordRpcConfig()
@@ -24,7 +24,7 @@ namespace Discord.Rpc
#else #else
WebSocketProvider = () => WebSocketProvider = () =>
{ {
throw new InvalidOperationException("The default websocket provider is not supported on this platform.\n" +
throw new InvalidOperationException("The default WebSocket provider is not supported on this platform.\n" +
"You must specify a WebSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+."); "You must specify a WebSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+.");
}; };
#endif #endif


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

@@ -117,6 +117,7 @@ namespace Discord.Commands.Builders
return this; return this;
} }


/// <exception cref="InvalidOperationException">Only the last parameter in a command may have the Remainder or Multiple flag.</exception>
internal CommandInfo Build(ModuleInfo info, CommandService service) internal CommandInfo Build(ModuleInfo info, CommandService service)
{ {
//Default name to primary alias //Default name to primary alias


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

@@ -40,17 +40,17 @@ namespace Discord.Commands
internal readonly LogManager _logManager; internal readonly LogManager _logManager;


/// <summary> /// <summary>
/// Represents all modules loaded within <see cref="CommandService" /> .
/// Represents all modules loaded within <see cref="CommandService" />.
/// </summary> /// </summary>
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x);


/// <summary> /// <summary>
/// Represents all commands loaded within <see cref="CommandService" /> .
/// Represents all commands loaded within <see cref="CommandService" />.
/// </summary> /// </summary>
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands);


/// <summary> /// <summary>
/// Represents all <see cref="TypeReader" /> loaded within <see cref="CommandService" /> .
/// Represents all <see cref="TypeReader" /> loaded within <see cref="CommandService" />.
/// </summary> /// </summary>
public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value);


@@ -122,12 +122,12 @@ namespace Discord.Commands
} }


/// <summary> /// <summary>
/// Add a command module from a <see cref="Type" /> .
/// Add a command module from a <see cref="Type" />.
/// </summary> /// </summary>
/// <typeparam name="T">The type of module.</typeparam> /// <typeparam name="T">The type of module.</typeparam>
/// <param name="services"> /// <param name="services">
/// The <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass /// The <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass
/// <see langword="null" /> .
/// <see langword="null" />.
/// </param> /// </param>
/// <returns> /// <returns>
/// A built module. /// A built module.
@@ -135,12 +135,12 @@ namespace Discord.Commands
public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services); public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services);


/// <summary> /// <summary>
/// Adds a command module from a <see cref="Type" /> .
/// Adds a command module from a <see cref="Type" />.
/// </summary> /// </summary>
/// <param name="type">The type of module.</param> /// <param name="type">The type of module.</param>
/// <param name="services"> /// <param name="services">
/// The <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass /// The <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass
/// <see langword="null" /> .
/// <see langword="null" />.
/// </param> /// </param>
/// <returns> /// <returns>
/// A built module. /// A built module.
@@ -174,12 +174,12 @@ namespace Discord.Commands
} }
} }
/// <summary> /// <summary>
/// Add command modules from an <see cref="Assembly" /> .
/// Add command modules from an <see cref="Assembly" />.
/// </summary> /// </summary>
/// <param name="assembly">The <see cref="Assembly" /> containing command modules.</param> /// <param name="assembly">The <see cref="Assembly" /> containing command modules.</param>
/// <param name="services"> /// <param name="services">
/// An <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass /// An <see cref="IServiceProvider" /> for your dependency injection solution, if using one - otherwise, pass
/// <see langword="null" /> .
/// <see langword="null" />.
/// </param> /// </param>
/// <returns> /// <returns>
/// A collection of built modules. /// A collection of built modules.


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

@@ -16,7 +16,7 @@ namespace Discord.Commands
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This object contains the information of a command. This can include the module of the command, various /// This object contains the information of a command. This can include the module of the command, various
/// descriptions regarding the command, and its <see cref="RunMode" /> .
/// descriptions regarding the command, and its <see cref="RunMode" />.
/// </remarks> /// </remarks>
[DebuggerDisplay("{Name,nq}")] [DebuggerDisplay("{Name,nq}")]
public class CommandInfo public class CommandInfo


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

@@ -23,6 +23,7 @@ namespace Discord.Commands
_commands = ImmutableArray.Create<CommandInfo>(); _commands = ImmutableArray.Create<CommandInfo>();
} }


/// <exception cref="InvalidOperationException">Cannot add commands to the root node.</exception>
public void AddCommand(CommandService service, string text, int index, CommandInfo command) public void AddCommand(CommandService service, string text, int index, CommandInfo command)
{ {
int nextSegment = NextSegment(text, index, service._separatorChar); int nextSegment = NextSegment(text, index, service._separatorChar);


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

@@ -17,10 +17,12 @@ namespace Discord.Commands
private readonly TryParseDelegate<T> _tryParse; private readonly TryParseDelegate<T> _tryParse;
private readonly float _score; private readonly float _score;


/// <exception cref="ArgumentOutOfRangeException"><typeparamref name="T"/> must be within the range [0, 1].</exception>
public PrimitiveTypeReader() public PrimitiveTypeReader()
: this(PrimitiveParsers.Get<T>(), 1) : this(PrimitiveParsers.Get<T>(), 1)
{ } { }


/// <exception cref="ArgumentOutOfRangeException"><paramref name="score"/> must be within the range [0, 1].</exception>
public PrimitiveTypeReader(TryParseDelegate<T> tryParse, float score) public PrimitiveTypeReader(TryParseDelegate<T> tryParse, float score)
{ {
if (score < 0 || score > 1) if (score < 0 || score > 1)


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

@@ -15,7 +15,7 @@ namespace Discord.Audio


/// <summary> Gets the current connection state of this client. </summary> /// <summary> Gets the current connection state of this client. </summary>
ConnectionState ConnectionState { get; } ConnectionState ConnectionState { get; }
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the voice websocket server. </summary>
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the voice WebSocket server. </summary>
int Latency { get; } int Latency { get; }
/// <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; }


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

@@ -13,7 +13,7 @@ namespace Discord
public static string GetApplicationIconUrl(ulong appId, string iconId) public static string GetApplicationIconUrl(ulong appId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null;
/// <summary> /// <summary>
/// Returns the user avatar URL based on the <paramref name="size"/> and <see cref="ImageFormat" /> .
/// Returns the user avatar URL based on the <paramref name="size"/> and <see cref="ImageFormat" />.
/// </summary> /// </summary>
public static string GetUserAvatarUrl(ulong userId, string avatarId, ushort size, ImageFormat format) public static string GetUserAvatarUrl(ulong userId, string avatarId, ushort size, ImageFormat format)
{ {
@@ -52,7 +52,7 @@ namespace Discord
=> $"{DiscordConfig.CDNUrl}emojis/{emojiId}.{(animated ? "gif" : "png")}"; => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.{(animated ? "gif" : "png")}";


/// <summary> /// <summary>
/// Returns the rich presence asset URL based on the asset ID and <see cref="ImageFormat" /> .
/// Returns the rich presence asset URL based on the asset ID and <see cref="ImageFormat" />.
/// </summary> /// </summary>
public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format) public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format)
{ {


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

@@ -1,7 +1,7 @@
namespace Discord.Commands namespace Discord.Commands
{ {
/// <summary> /// <summary>
/// Represents the context of a command. This may include the client, guild, channel, user, and message.
/// Represents a context of a command. This may include the client, guild, channel, user, and message.
/// </summary> /// </summary>
public interface ICommandContext public interface ICommandContext
{ {


+ 1
- 1
src/Discord.Net.Core/Entities/Activities/Game.cs View File

@@ -18,7 +18,7 @@ namespace Discord
/// Creates a <see cref="Game"/> with the provided <paramref name="name"/> and <see cref="ActivityType"/>. /// Creates a <see cref="Game"/> with the provided <paramref name="name"/> and <see cref="ActivityType"/>.
/// </summary> /// </summary>
/// <param name="name">The name of the game.</param> /// <param name="name">The name of the game.</param>
/// <param name="type">The type of activity. Default is <see cref="Discord.ActivityType.Playing" /> .</param>
/// <param name="type">The type of activity. Default is <see cref="Discord.ActivityType.Playing" />.</param>
public Game(string name, ActivityType type = ActivityType.Playing) public Game(string name, ActivityType type = ActivityType.Playing)
{ {
Name = name; Name = name;


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

@@ -1,7 +1,7 @@
namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
/// Properties that are used to reorder an <see cref="IGuildChannel" /> .
/// Properties that are used to reorder an <see cref="IGuildChannel" />.
/// </summary> /// </summary>
public class ReorderChannelProperties public class ReorderChannelProperties
{ {


+ 2
- 2
src/Discord.Net.Core/Entities/Emotes/EmoteProperties.cs View File

@@ -8,11 +8,11 @@ namespace Discord
public class EmoteProperties public class EmoteProperties
{ {
/// <summary> /// <summary>
/// Gets or sets the name of the <see cref="Emote" /> .
/// Gets or sets the name of the <see cref="Emote" />.
/// </summary> /// </summary>
public Optional<string> Name { get; set; } public Optional<string> Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the roles that can access this <see cref="Emote" /> .
/// Gets or sets the roles that can access this <see cref="Emote" />.
/// </summary> /// </summary>
public Optional<IEnumerable<IRole>> Roles { get; set; } public Optional<IEnumerable<IRole>> Roles { get; set; }
} }


+ 154
- 18
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
/// Represents a generic guild object.
/// Represents a generic guild/server.
/// </summary> /// </summary>
public interface IGuild : IDeletable, ISnowflakeEntity public interface IGuild : IDeletable, ISnowflakeEntity
{ {
@@ -23,7 +23,7 @@ namespace Discord
/// Determines if this guild is embeddable (i.e. can use widget). /// Determines if this guild is embeddable (i.e. can use widget).
/// </summary> /// </summary>
/// <returns> /// <returns>
/// Returns <see langword="true"/> if this guild can be embedded via widgets.
/// <see langword="true"/> if this guild can be embedded via widgets; otherwise <see langword="false"/>.
/// </returns> /// </returns>
bool IsEmbeddable { get; } bool IsEmbeddable { get; }
/// <summary> /// <summary>
@@ -91,58 +91,91 @@ namespace Discord
/// <summary> /// <summary>
/// Gets the <see cref="IAudioClient" /> currently associated with this guild. /// Gets the <see cref="IAudioClient" /> currently associated with this guild.
/// </summary> /// </summary>
/// <returns>
/// <see cref="IAudioClient" /> currently associated with this guild.
/// </returns>
IAudioClient AudioClient { get; } IAudioClient AudioClient { get; }
/// <summary> /// <summary>
/// Gets the built-in role containing all users in this guild. /// Gets the built-in role containing all users in this guild.
/// </summary> /// </summary>
/// <returns>
/// Built-in role that represents an @everyone role in this guild.
/// </returns>
IRole EveryoneRole { get; } IRole EveryoneRole { get; }
/// <summary> /// <summary>
/// Gets a collection of all custom emotes for this guild. /// Gets a collection of all custom emotes for this guild.
/// </summary> /// </summary>
/// <returns>
/// A collection of all custom emotes for this guild.
/// </returns>
IReadOnlyCollection<GuildEmote> Emotes { get; } IReadOnlyCollection<GuildEmote> Emotes { get; }
/// <summary> /// <summary>
/// Gets a collection of all extra features added to this guild. /// Gets a collection of all extra features added to this guild.
/// </summary> /// </summary>
/// <returns>
/// A collection of enabled features in this guild.
/// </returns>
IReadOnlyCollection<string> Features { get; } IReadOnlyCollection<string> Features { get; }
/// <summary> /// <summary>
/// Gets a collection of all roles in this guild. /// Gets a collection of all roles in this guild.
/// </summary> /// </summary>
/// <returns>
/// A collection of roles found within this guild.
/// </returns>
IReadOnlyCollection<IRole> Roles { get; } IReadOnlyCollection<IRole> Roles { get; }


/// <summary> /// <summary>
/// Modifies this guild. /// Modifies this guild.
/// </summary> /// </summary>
/// <param name="func">The properties to modify the guild with.</param>
/// <param name="func">The delegate containing the properties to modify the guild with.</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>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null); Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null);
/// <summary> /// <summary>
/// Modifies this guild's embed channel. /// Modifies this guild's embed channel.
/// </summary> /// </summary>
/// <param name="func">The properties to modify the guild widget with.</param>
/// <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> /// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null); Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null);
/// <summary> /// <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 to modify the channel positions with.</param>
/// <param name="args">The properties used to modify the channel positions with.</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>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null); Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null);
/// <summary> /// <summary>
/// Bulk modifies the order of roles in this guild. /// Bulk modifies the order of roles in this guild.
/// </summary> /// </summary>
/// <param name="args">The properties to modify the role positions with.</param>
/// <param name="args">The properties used to modify the role positions with.</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>
Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null); Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null);
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
/// <summary> /// <summary>
/// Leaves this guild. If you are the owner, use <see cref="IDeletable.DeleteAsync" /> instead. /// Leaves this guild. If you are the owner, use <see cref="IDeletable.DeleteAsync" /> instead.
/// </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>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task LeaveAsync(RequestOptions options = null); Task LeaveAsync(RequestOptions options = null);


/// <summary> /// <summary>
/// Gets a collection of all users banned on this guild. /// Gets a collection of all users banned on 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>
/// An awaitable <see cref="Task"/> containing a collection of banned users with reasons.
/// </returns>
Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null); Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null);
/// <summary> /// <summary>
/// Bans the provided user from this guild and optionally prunes their recent messages. /// Bans the provided user from this guild and optionally prunes their recent messages.
@@ -154,6 +187,9 @@ namespace Discord
/// <param name="reason">The reason of the ban to be written in the audit log.</param> /// <param name="reason">The reason of the ban to be written in the audit log.</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="ArgumentException"><paramref name="pruneDays" /> is not between 0 to 7.</exception> /// <exception cref="ArgumentException"><paramref name="pruneDays" /> is not between 0 to 7.</exception>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null);
/// <summary> /// <summary>
/// Bans the provided user ID from this guild and optionally prunes their recent messages. /// Bans the provided user ID from this guild and optionally prunes their recent messages.
@@ -165,14 +201,27 @@ namespace Discord
/// <param name="reason">The reason of the ban to be written in the audit log.</param> /// <param name="reason">The reason of the ban to be written in the audit log.</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="ArgumentException"><paramref name="pruneDays" /> is not between 0 to 7.</exception> /// <exception cref="ArgumentException"><paramref name="pruneDays" /> is not between 0 to 7.</exception>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null); Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null);
/// <summary> /// <summary>
/// Unbans the provided user if they are currently banned. /// Unbans the provided user if they are currently banned.
/// </summary> /// </summary>
/// <param name="user">The user to be unbanned.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task RemoveBanAsync(IUser user, RequestOptions options = null); Task RemoveBanAsync(IUser user, RequestOptions options = null);
/// <summary> /// <summary>
/// Unbans the provided user ID if it is currently banned. /// Unbans the provided user ID if it is currently banned.
/// </summary> /// </summary>
/// <param name="userId">The snowflake ID of the user to be unbanned.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task RemoveBanAsync(ulong userId, RequestOptions options = null); Task RemoveBanAsync(ulong userId, RequestOptions options = null);


/// <summary> /// <summary>
@@ -182,15 +231,22 @@ namespace Discord
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing a collection of generic channels found within this guild.
/// </returns>
Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets the channel in this guild with the provided ID, or <see langword="null" /> if not found.
/// Gets the channel in this guild with the provided ID.
/// </summary> /// </summary>
/// <param name="id">The channel ID.</param> /// <param name="id">The channel ID.</param>
/// <param name="mode"> /// <param name="mode">
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing the generic channel with the specified ID, or
/// <see langword="null"/> if none is found.
/// </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>
/// Gets a collection of all text channels in this guild. /// Gets a collection of all text channels in this guild.
@@ -199,6 +255,9 @@ namespace Discord
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing a collection of text channels found within this guild.
/// </returns>
Task<IReadOnlyCollection<ITextChannel>> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IReadOnlyCollection<ITextChannel>> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets a text channel in this guild with the provided ID, or <see langword="null" /> if not found. /// Gets a text channel in this guild with the provided ID, or <see langword="null" /> if not found.
@@ -208,6 +267,10 @@ namespace Discord
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing the text channel with the specified ID, or
/// <see langword="null"/> if none is found.
/// </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>
/// Gets a collection of all voice channels in this guild. /// Gets a collection of all voice channels in this guild.
@@ -216,6 +279,9 @@ namespace Discord
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing a collection of voice channels found within this guild.
/// </returns>
Task<IReadOnlyCollection<IVoiceChannel>> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IReadOnlyCollection<IVoiceChannel>> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets a collection of all category channels in this guild. /// Gets a collection of all category channels in this guild.
@@ -224,68 +290,97 @@ namespace Discord
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing a collection of category channels found within this guild.
/// </returns>
Task<IReadOnlyCollection<ICategoryChannel>> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IReadOnlyCollection<ICategoryChannel>> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets the voice channel in this guild with the provided ID, or <see langword="null" /> if not found.
/// Gets the voice channel in this guild with the provided ID.
/// </summary> /// </summary>
/// <param name="id">The text channel ID.</param> /// <param name="id">The text channel ID.</param>
/// <param name="mode"> /// <param name="mode">
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing the voice channel with the specified ID, or
/// <see langword="null"/> if none is found.
/// </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>
/// Gets the voice AFK channel in this guild with the provided ID, or <see langword="null" /> if not found.
/// Gets the AFK voice channel in this guild.
/// </summary> /// </summary>
/// <param name="mode"> /// <param name="mode">
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing the AFK voice channel set within this guild, or
/// <see langword="null"/> if none is set.
/// </returns>
Task<IVoiceChannel> GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IVoiceChannel> GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets the default system text channel in this guild with the provided ID, or <see langword="null" /> if
/// none is set.
/// Gets the default system text channel in this guild with the provided ID.
/// </summary> /// </summary>
/// <param name="mode"> /// <param name="mode">
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing the system channel within this guild, or
/// <see langword="null" /> if none is set.
/// </returns>
Task<ITextChannel> GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<ITextChannel> GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets the top viewable text channel in this guild with the provided ID, or <see langword="null" /> if not
/// found.
/// Gets the top viewable text channel in this guild with the provided ID.
/// </summary> /// </summary>
/// <param name="mode"> /// <param name="mode">
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing the first viewable text channel in this guild, or
/// <see langword="null"/> if none is found.
/// </returns>
Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild, or
/// <see langword="null" /> if none is set.
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary> /// </summary>
/// <param name="mode"> /// <param name="mode">
/// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache. /// The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.
/// </param> /// </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>
/// An awaitable <see cref="Task"/> containing the embed channel set within the server's widget settings, or
/// <see langword="null"/> if none is set.
/// </returns>
Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Creates a new text channel. /// Creates a new text channel.
/// </summary> /// </summary>
/// <param name="name">The new name for the text channel.</param> /// <param name="name">The new name for the text channel.</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>
/// An awaitable <see cref="Task"/> containing the newly created text channel.
/// </returns>
Task<ITextChannel> CreateTextChannelAsync(string name, RequestOptions options = null); Task<ITextChannel> CreateTextChannelAsync(string name, RequestOptions options = null);
/// <summary> /// <summary>
/// Creates a new voice channel. /// Creates a new voice channel.
/// </summary> /// </summary>
/// <param name="name">The new name for the voice channel.</param> /// <param name="name">The new name for the voice channel.</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>
/// An awaitable <see cref="Task"/> containing the newly created voice channel.
/// </returns>
Task<IVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null); Task<IVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null);
/// <summary> /// <summary>
/// Creates a new channel category. /// Creates a new channel category.
/// </summary> /// </summary>
/// <param name="name">The new name for the category.</param> /// <param name="name">The new name for the category.</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>
/// An awaitable <see cref="Task"/> containing the newly created category channel.
/// </returns>
Task<ICategoryChannel> CreateCategoryAsync(string name, RequestOptions options = null); Task<ICategoryChannel> CreateCategoryAsync(string name, RequestOptions options = null);


Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null); Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null);
@@ -309,13 +404,24 @@ namespace Discord
/// <param name="color">The color of the role.</param> /// <param name="color">The color of the role.</param>
/// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param> /// <param name="isHoisted">Whether the role is separated from others on the sidebar.</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>
/// An awaitable <see cref="Task"/> containing the newly crated role.
/// </returns>
Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null);


/// <summary> /// <summary>
/// Gets a collection of all users in this guild. /// Gets a collection of all users in this guild.
/// </summary> /// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
/// <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> /// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing a collection of users found within this guild.
/// </returns>
/// <remarks>
/// This may return an incomplete list on the WebSocket implementation because Discord only sends offline
/// users on large guilds.
/// </remarks>
Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets the user in this guild with the provided ID, or <see langword="null"/> if not found. /// Gets the user in this guild with the provided ID, or <see langword="null"/> if not found.
@@ -323,22 +429,34 @@ namespace Discord
/// <param name="id">The user ID.</param> /// <param name="id">The user ID.</param>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> /// <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> /// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing the guild user with the specified ID, otherwise <see langword="null"/>.
/// </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>
/// Gets the current user for this guild. /// Gets the current user for this guild.
/// </summary> /// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> /// <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> /// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing the currently logged-in user within this guild.
/// </returns>
Task<IGuildUser> GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IGuildUser> GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets the owner of this guild. /// Gets the owner of this guild.
/// </summary> /// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> /// <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> /// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing the owner of this guild.
/// </returns>
Task<IGuildUser> GetOwnerAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task<IGuildUser> GetOwnerAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary> /// <summary>
/// Downloads all users for this guild if the current list is incomplete. /// Downloads all users for this guild if the current list is incomplete.
/// </summary> /// </summary>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task DownloadUsersAsync(); Task DownloadUsersAsync();
/// <summary> /// <summary>
/// Removes all users from this guild if they have not logged on in a provided number of /// Removes all users from this guild if they have not logged on in a provided number of
@@ -349,7 +467,7 @@ namespace Discord
/// <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>
/// <returns> /// <returns>
/// The number of users removed from this guild.
/// An awaitable <see cref="Task"/> containing the number of users to 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);


@@ -358,11 +476,17 @@ namespace Discord
/// </summary> /// </summary>
/// <param name="id">The webhook ID.</param> /// <param name="id">The webhook ID.</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>
/// An awaitable <see cref="Task"/> containing the webhook with the specified ID, otherwise <see langword="null"/>.
/// </returns>
Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null); Task<IWebhook> GetWebhookAsync(ulong id, RequestOptions options = null);
/// <summary> /// <summary>
/// Gets a collection of all webhook from this guild. /// Gets a collection of all webhook from 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>
/// An awaitable <see cref="Task"/> containing a collection of webhooks found within the guild.
/// </returns>
Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null);


/// <summary> /// <summary>
@@ -370,6 +494,9 @@ namespace Discord
/// </summary> /// </summary>
/// <param name="id">The guild emote ID.</param> /// <param name="id">The guild emote ID.</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>
/// An awaitable <see cref="Task"/> containing the emote found with the specified ID, or <see langword="null"/> if not found.
/// </returns>
Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null); Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null);
/// <summary> /// <summary>
/// Creates a new <see cref="GuildEmote"/> in this guild. /// Creates a new <see cref="GuildEmote"/> in this guild.
@@ -378,20 +505,29 @@ namespace Discord
/// <param name="image">The image of the new emote.</param> /// <param name="image">The image of the new emote.</param>
/// <param name="roles">The roles to limit the emote usage to.</param> /// <param name="roles">The roles to limit the emote usage to.</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>
/// An awaitable <see cref="Task"/> containing the created emote.
/// </returns>
Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null); Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null);


/// <summary> /// <summary>
/// Modifies an existing <see cref="GuildEmote"/> in this guild. /// Modifies an existing <see cref="GuildEmote"/> in this guild.
/// </summary> /// </summary>
/// <param name="emote">The emote to be modified.</param> /// <param name="emote">The emote to be modified.</param>
/// <param name="func">The properties to modify the emote with.</param>
/// <param name="func">The delegate containing the properties to modify the emote with.</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>
/// An awaitable <see cref="Task"/> containing the newly modified emote.
/// </returns>
Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null); Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null);
/// <summary> /// <summary>
/// Deletes an existing <see cref="GuildEmote"/> from this guild. /// Deletes an existing <see cref="GuildEmote"/> from this guild.
/// </summary> /// </summary>
/// <param name="emote">The emote to delete.</param> /// <param name="emote">The emote to delete.</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>
/// An awaitable <see cref="Task"/>.
/// </returns>
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null);
} }
} }

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

@@ -3,7 +3,7 @@ using System.Threading.Tasks;
namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
/// Represents whether the object is updatable or not.
/// Defines whether the object is updateable or not.
/// </summary> /// </summary>
public interface IUpdateable public interface IUpdateable
{ {


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

@@ -33,9 +33,9 @@ namespace Discord
/// <param name="path">The path to the file.</param> /// <param name="path">The path to the file.</param>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// <paramref name="path" /> is a zero-length string, contains only white space, or contains one or more invalid /// <paramref name="path" /> is a zero-length string, contains only white space, or contains one or more invalid
/// characters as defined by <see cref="Path.GetInvalidPathChars" /> .
/// characters as defined by <see cref="Path.GetInvalidPathChars" />.
/// </exception> /// </exception>
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <see langword="null" /> .</exception>
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <see langword="null" />.</exception>
/// <exception cref="PathTooLongException"> /// <exception cref="PathTooLongException">
/// The specified path, file name, or both exceed the system-defined maximum length. For example, on /// The specified path, file name, or both exceed the system-defined maximum length. For example, on
/// Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 /// Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260


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

@@ -172,7 +172,7 @@ namespace Discord
} }


/// <summary> /// <summary>
/// Sets the title of an <see cref="Embed" /> .
/// Sets the title of an <see cref="Embed" />.
/// </summary> /// </summary>
/// <param name="title">The title to be set.</param> /// <param name="title">The title to be set.</param>
/// <returns> /// <returns>


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

@@ -3,7 +3,7 @@ using System.Diagnostics;
namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
/// Represents a field for an <see cref="Embed" /> .
/// Represents a field for an <see cref="Embed" />.
/// </summary> /// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedField public struct EmbedField


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

@@ -3,7 +3,7 @@ using System.Diagnostics;
namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
/// A video featured in an <see cref="Embed" /> .
/// A video featured in an <see cref="Embed" />.
/// </summary> /// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedVideo public struct EmbedVideo


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

@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
/// Represents a Discord message object.
/// Represents a message object.
/// </summary> /// </summary>
public interface IMessage : ISnowflakeEntity, IDeletable public interface IMessage : ISnowflakeEntity, IDeletable
{ {


+ 13
- 12
src/Discord.Net.Core/Entities/Messages/MessageProperties.cs View File

@@ -4,20 +4,21 @@ namespace Discord
/// Properties that are used to modify an <see cref="IUserMessage" /> with the specified changes. /// Properties that are used to modify an <see cref="IUserMessage" /> with the specified changes.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The content of a message can be cleared with String.Empty; if and only if an Embed is present.
/// The content of a message can be cleared with <see cref="string.Empty"/> if and only if an <see cref="Discord.Embed"> is present.
/// </remarks> /// </remarks>
/// <example> /// <example>
/// <code lang="c#"> /// <code lang="c#">
/// var message = await ReplyAsync("abc");
/// await message.ModifyAsync(x =&gt;
/// {
/// x.Content = "";
/// x.Embed = new EmbedBuilder()
/// .WithColor(new Color(40, 40, 120))
/// .WithAuthor(a =&gt; a.Name = "foxbot")
/// .WithTitle("Embed!")
/// .WithDescription("This is an embed.");
/// });
/// var message = await ReplyAsync("abc");
/// await message.ModifyAsync(x =&gt;
/// {
/// x.Content = "";
/// x.Embed = new EmbedBuilder()
/// .WithColor(new Color(40, 40, 120))
/// .WithAuthor(a =&gt; a.Name = "foxbot")
/// .WithTitle("Embed!")
/// .WithDescription("This is an embed.")
/// .Build();
/// });
/// </code> /// </code>
/// </example> /// </example>
public class MessageProperties public class MessageProperties
@@ -26,7 +27,7 @@ namespace Discord
/// Gets or sets the content of the message. /// Gets or sets the content of the message.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This must be less than 2000 characters.
/// This must be less than the constant defined by <see cref="DiscordConfig.MaxMessageSize"/>.
/// </remarks> /// </remarks>
public Optional<string> Content { get; set; } public Optional<string> Content { get; set; }
/// <summary> /// <summary>


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

@@ -19,7 +19,8 @@ namespace Discord
public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000);
/// <summary> Gets a <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 channelType. </summary>
/// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for a given channel type. </summary>
/// <exception cref="ArgumentException">Unknown channel type.</exception>
public static ChannelPermissions All(IChannel channel) public static ChannelPermissions All(IChannel channel)
{ {
switch (channel) switch (channel)


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

@@ -1,7 +1,7 @@
namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
/// Properties that are used to reorder an <see cref="IRole" /> .
/// Properties that are used to reorder an <see cref="IRole" />.
/// </summary> /// </summary>
public class ReorderRoleProperties public class ReorderRoleProperties
{ {


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

@@ -23,7 +23,7 @@ namespace Discord
/// </remarks> /// </remarks>
public Optional<string> Name { get; set; } public Optional<string> Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the role's <see cref="GuildPermission" /> .
/// Gets or sets the role's <see cref="GuildPermission" />.
/// </summary> /// </summary>
public Optional<GuildPermissions> Permissions { get; set; } public Optional<GuildPermissions> Permissions { get; set; }
/// <summary> /// <summary>


+ 5
- 5
src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs View File

@@ -7,10 +7,10 @@ namespace Discord
/// </summary> /// </summary>
/// <example> /// <example>
/// <code lang="c#"> /// <code lang="c#">
/// await (Context.User as IGuildUser)?.ModifyAsync(x =&gt;
/// {
/// x.Nickname = $"festive {Context.User.Username}";
/// });
/// await guildUser.ModifyAsync(x =&gt;
/// {
/// x.Nickname = $"festive {guildUser.Username}";
/// });
/// </code> /// </code>
/// </example> /// </example>
/// <seealso cref="T:Discord.IGuildUser" /> /// <seealso cref="T:Discord.IGuildUser" />
@@ -35,7 +35,7 @@ namespace Discord
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// To clear the user's nickname, this value can be set to <see langword="null" /> or /// To clear the user's nickname, this value can be set to <see langword="null" /> or
/// <see cref="string.Empty" /> .
/// <see cref="string.Empty" />.
/// </remarks> /// </remarks>
public Optional<string> Nickname { get; set; } public Optional<string> Nickname { get; set; }
/// <summary> /// <summary>


+ 2
- 2
src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs View File

@@ -27,14 +27,14 @@ namespace Discord
/// Gets or sets the channel for this webhook. /// Gets or sets the channel for this webhook.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This field is not used when authenticated with <see cref="Discord.TokenType.Webhook" /> .
/// This field is not used when authenticated with <see cref="Discord.TokenType.Webhook" />.
/// </remarks> /// </remarks>
public Optional<ITextChannel> Channel { get; set; } public Optional<ITextChannel> Channel { get; set; }
/// <summary> /// <summary>
/// Gets or sets the channel ID for this webhook. /// Gets or sets the channel ID for this webhook.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This field is not used when authenticated with <see cref="Discord.TokenType.Webhook" /> .
/// This field is not used when authenticated with <see cref="Discord.TokenType.Webhook" />.
/// </remarks> /// </remarks>
public Optional<ulong> ChannelId { get; set; } public Optional<ulong> ChannelId { get; set; }
} }


+ 13
- 7
src/Discord.Net.Core/Utils/ConcurrentHashSet.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -157,12 +157,16 @@ namespace Discord
: this(collection, EqualityComparer<T>.Default) { } : this(collection, EqualityComparer<T>.Default) { }
public ConcurrentHashSet(IEqualityComparer<T> comparer) public ConcurrentHashSet(IEqualityComparer<T> comparer)
: this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { } : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { }
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <see langword="null"/></exception>
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
: this(comparer) : this(comparer)
{ {
if (collection == null) throw new ArgumentNullException(nameof(collection)); if (collection == null) throw new ArgumentNullException(nameof(collection));
InitializeFromCollection(collection); InitializeFromCollection(collection);
} }
/// <exception cref="ArgumentNullException">
/// <paramref name="collection" /> or <paramref name="comparer" /> is <see langword="null" />
/// </exception>
public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer) public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer)
: this(concurrencyLevel, DefaultCapacity, false, comparer) : this(concurrencyLevel, DefaultCapacity, false, comparer)
{ {
@@ -197,7 +201,7 @@ namespace Discord
{ {
foreach (var value in collection) foreach (var value in collection)
{ {
if (value == null) throw new ArgumentNullException("key");
if (value == null) throw new ArgumentNullException(nameof(value));


if (!TryAddInternal(value, _comparer.GetHashCode(value), false)) if (!TryAddInternal(value, _comparer.GetHashCode(value), false))
throw new ArgumentException(); throw new ArgumentException();
@@ -206,10 +210,10 @@ namespace Discord
if (_budget == 0) if (_budget == 0)
_budget = _tables._buckets.Length / _tables._locks.Length; _budget = _tables._buckets.Length / _tables._locks.Length;
} }
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/></exception>
public bool ContainsKey(T value) public bool ContainsKey(T value)
{ {
if (value == null) throw new ArgumentNullException("key");
if (value == null) throw new ArgumentNullException(nameof(value));
return ContainsKeyInternal(value, _comparer.GetHashCode(value)); return ContainsKeyInternal(value, _comparer.GetHashCode(value));
} }
private bool ContainsKeyInternal(T value, int hashcode) private bool ContainsKeyInternal(T value, int hashcode)
@@ -230,9 +234,10 @@ namespace Discord
return false; return false;
} }


/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/></exception>
public bool TryAdd(T value) public bool TryAdd(T value)
{ {
if (value == null) throw new ArgumentNullException("key");
if (value == null) throw new ArgumentNullException(nameof(value));
return TryAddInternal(value, _comparer.GetHashCode(value), true); return TryAddInternal(value, _comparer.GetHashCode(value), true);
} }
private bool TryAddInternal(T value, int hashcode, bool acquireLock) private bool TryAddInternal(T value, int hashcode, bool acquireLock)
@@ -279,9 +284,10 @@ namespace Discord
} }
} }


/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/></exception>
public bool TryRemove(T value) public bool TryRemove(T value)
{ {
if (value == null) throw new ArgumentNullException("key");
if (value == null) throw new ArgumentNullException(nameof(value));
return TryRemoveInternal(value); return TryRemoveInternal(value);
} }
private bool TryRemoveInternal(T value) private bool TryRemoveInternal(T value)
@@ -467,4 +473,4 @@ namespace Discord
Monitor.Exit(_tables._locks[i]); Monitor.Exit(_tables._locks[i]);
} }
} }
}
}

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

@@ -11,6 +11,7 @@ namespace Discord
private readonly T _value; private readonly T _value;


/// <summary> Gets the value for this parameter. </summary> /// <summary> Gets the value for this parameter. </summary>
/// <exception cref="InvalidOperationException" accessor="get">This property has no value set.</exception>
public T Value public T Value
{ {
get get


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

@@ -183,7 +183,7 @@ namespace Discord
} }


// Bulk Delete // Bulk Delete
/// <exception cref="ArgumentOutOfRangeException">Messages are younger than 2 weeks..</exception>
/// <exception cref="ArgumentOutOfRangeException">Messages are younger than 2 weeks.</exception>
public static void YoungerThanTwoWeeks(ulong[] collection, string name) public static void YoungerThanTwoWeeks(ulong[] collection, string name)
{ {
var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14)));
@@ -194,7 +194,7 @@ namespace Discord
throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old.");
} }
} }
/// <exception cref="ArgumentException">The everyone role cannot be assigned to a user</exception>
/// <exception cref="ArgumentException">The everyone role cannot be assigned to a user.</exception>
public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name) public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name)
{ {
for (var i = 0; i < roles.Length; i++) for (var i = 0; i < roles.Length; i++)


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

@@ -1,3 +1,4 @@
using System;
using Discord.API.Rest; using Discord.API.Rest;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -24,6 +25,7 @@ namespace Discord.Rest
return RestChannel.Create(client, model); return RestChannel.Create(client, model);
return null; return null;
} }
/// <exception cref="InvalidOperationException">Unexpected channel type.</exception>
public static async Task<IReadOnlyCollection<IRestPrivateChannel>> GetPrivateChannelsAsync(BaseDiscordClient client, RequestOptions options) public static async Task<IReadOnlyCollection<IRestPrivateChannel>> GetPrivateChannelsAsync(BaseDiscordClient client, RequestOptions options)
{ {
var models = await client.ApiClient.GetMyPrivateChannelsAsync(options).ConfigureAwait(false); var models = await client.ApiClient.GetMyPrivateChannelsAsync(options).ConfigureAwait(false);


+ 16
- 3
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -58,6 +58,9 @@ namespace Discord.API


SetBaseUrl(DiscordConfig.APIUrl); SetBaseUrl(DiscordConfig.APIUrl);
} }

/// <exception cref="ArgumentException">Unknown OAuth token type.</exception>
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
internal void SetBaseUrl(string baseUrl) internal void SetBaseUrl(string baseUrl)
{ {
RestClient = _restClientProvider(baseUrl); RestClient = _restClientProvider(baseUrl);
@@ -65,6 +68,7 @@ namespace Discord.API
RestClient.SetHeader("user-agent", UserAgent); RestClient.SetHeader("user-agent", UserAgent);
RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken));
} }
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception>
internal static string GetPrefixedToken(TokenType tokenType, string token) internal static string GetPrefixedToken(TokenType tokenType, string token)
{ {
switch (tokenType) switch (tokenType)
@@ -76,7 +80,7 @@ namespace Discord.API
case TokenType.Bearer: case TokenType.Bearer:
return $"Bearer {token}"; return $"Bearer {token}";
default: default:
throw new ArgumentException("Unknown OAuth token type", nameof(tokenType));
throw new ArgumentException("Unknown OAuth token type.", nameof(tokenType));
} }
} }
internal virtual void Dispose(bool disposing) internal virtual void Dispose(bool disposing)
@@ -463,6 +467,7 @@ namespace Discord.API
endpoint = () => $"channels/{channelId}/messages?limit={limit}"; endpoint = () => $"channels/{channelId}/messages?limit={limit}";
return await SendAsync<IReadOnlyCollection<Message>>("GET", endpoint, ids, options: options).ConfigureAwait(false); return await SendAsync<IReadOnlyCollection<Message>>("GET", endpoint, ids, options: options).ConfigureAwait(false);
} }
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public async Task<Message> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) public async Task<Message> CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null)
{ {
Preconditions.NotNull(args, nameof(args)); Preconditions.NotNull(args, nameof(args));
@@ -471,12 +476,14 @@ namespace Discord.API
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content));


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


var ids = new BucketIds(channelId: channelId); var ids = new BucketIds(channelId: channelId);
return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
} }
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
/// <exception cref="InvalidOperationException">This operation may only be called with a <see cref="TokenType.Webhook"/> token.</exception>
public async Task<Message> CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null) public async Task<Message> CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null)
{ {
if (AuthTokenType != TokenType.Webhook) if (AuthTokenType != TokenType.Webhook)
@@ -488,11 +495,12 @@ namespace Discord.API
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content));


if (args.Content?.Length > DiscordConfig.MaxMessageSize) if (args.Content?.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content));
throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content));
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
return await SendJsonAsync<Message>("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); return await SendJsonAsync<Message>("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
} }
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null)
{ {
Preconditions.NotNull(args, nameof(args)); Preconditions.NotNull(args, nameof(args));
@@ -507,6 +515,9 @@ namespace Discord.API
var ids = new BucketIds(channelId: channelId); var ids = new BucketIds(channelId: channelId);
return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
} }

/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
/// <exception cref="InvalidOperationException">This operation may only be called with a <see cref="TokenType.Webhook"/> token.</exception>
public async Task<Message> UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null) public async Task<Message> UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null)
{ {
if (AuthTokenType != TokenType.Webhook) if (AuthTokenType != TokenType.Webhook)
@@ -559,6 +570,7 @@ namespace Discord.API
break; break;
} }
} }
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null) public async Task<Message> ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null)
{ {
Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(channelId, 0, nameof(channelId));
@@ -1267,6 +1279,7 @@ namespace Discord.API
} }


//Helpers //Helpers
/// <exception cref="InvalidOperationException">Client is not logged in.</exception>
protected void CheckState() protected void CheckState()
{ {
if (LoginState != LoginState.LoggedIn) if (LoginState != LoginState.LoggedIn)


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

@@ -22,12 +22,14 @@ namespace Discord.Rest
ApiClient.Dispose(); ApiClient.Dispose();
} }


/// <inheritdoc />
internal override async Task OnLoginAsync(TokenType tokenType, string token) internal override async Task OnLoginAsync(TokenType tokenType, string token)
{ {
var user = await ApiClient.GetMyUserAsync(new RequestOptions { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false); var user = await ApiClient.GetMyUserAsync(new RequestOptions { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
ApiClient.CurrentUserId = user.Id; ApiClient.CurrentUserId = user.Id;
base.CurrentUser = RestSelfUser.Create(this, user); base.CurrentUser = RestSelfUser.Create(this, user);
} }
/// <inheritdoc />
internal override Task OnLogoutAsync() internal override Task OnLogoutAsync()
{ {
_applicationInfo = null; _applicationInfo = null;
@@ -80,9 +82,11 @@ namespace Discord.Rest
=> ClientHelper.GetWebhookAsync(this, id, options); => ClientHelper.GetWebhookAsync(this, id, options);


//IDiscordClient //IDiscordClient
/// <inheritdoc />
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
=> await GetApplicationInfoAsync(options).ConfigureAwait(false); => await GetApplicationInfoAsync(options).ConfigureAwait(false);


/// <inheritdoc />
async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -90,6 +94,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -97,6 +102,7 @@ namespace Discord.Rest
else else
return ImmutableArray.Create<IPrivateChannel>(); return ImmutableArray.Create<IPrivateChannel>();
} }
/// <inheritdoc />
async Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -104,6 +110,7 @@ namespace Discord.Rest
else else
return ImmutableArray.Create<IDMChannel>(); return ImmutableArray.Create<IDMChannel>();
} }
/// <inheritdoc />
async Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -112,12 +119,15 @@ namespace Discord.Rest
return ImmutableArray.Create<IGroupChannel>(); return ImmutableArray.Create<IGroupChannel>();
} }


/// <inheritdoc />
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options)
=> await GetConnectionsAsync(options).ConfigureAwait(false); => await GetConnectionsAsync(options).ConfigureAwait(false);


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


/// <inheritdoc />
async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -125,6 +135,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -132,9 +143,11 @@ namespace Discord.Rest
else else
return ImmutableArray.Create<IGuild>(); return ImmutableArray.Create<IGuild>();
} }
/// <inheritdoc />
async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options)
=> await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false);


/// <inheritdoc />
async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -143,11 +156,14 @@ namespace Discord.Rest
return null; return null;
} }


/// <inheritdoc />
async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options)
=> await GetVoiceRegionsAsync(options).ConfigureAwait(false); => await GetVoiceRegionsAsync(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
=> await GetVoiceRegionAsync(id, options).ConfigureAwait(false); => await GetVoiceRegionAsync(id, options).ConfigureAwait(false);


/// <inheritdoc />
async Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) async Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options)
=> await GetWebhookAsync(id, options).ConfigureAwait(false); => await GetWebhookAsync(id, options).ConfigureAwait(false);
} }


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

@@ -7,7 +7,6 @@ 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; using UserModel = Discord.API.User;
using WebhookModel = Discord.API.Webhook;


namespace Discord.Rest namespace Discord.Rest
{ {
@@ -77,14 +76,8 @@ namespace Discord.Rest
int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
{ {
var args = new CreateChannelInviteParams { IsTemporary = isTemporary, IsUnique = isUnique }; var args = new CreateChannelInviteParams { IsTemporary = isTemporary, IsUnique = isUnique };
if (maxAge.HasValue)
args.MaxAge = maxAge.Value;
else
args.MaxAge = 0;
if (maxUses.HasValue)
args.MaxUses = maxUses.Value;
else
args.MaxUses = 0;
args.MaxAge = maxAge.GetValueOrDefault(0);
args.MaxUses = maxUses.GetValueOrDefault(0);
var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false);
return RestInviteMetadata.Create(client, null, channel, model); return RestInviteMetadata.Create(client, null, channel, model);
} }
@@ -160,6 +153,7 @@ namespace Discord.Rest
return builder.ToImmutable(); return builder.ToImmutable();
} }


/// <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, RequestOptions options) string text, bool isTTS, Embed embed, RequestOptions options)
{ {
@@ -169,6 +163,30 @@ namespace Discord.Rest
} }


#if FILESYSTEM #if FILESYSTEM
/// <exception cref="ArgumentException">
/// <paramref name="filePath" /> is a zero-length string, contains only white space, or contains one or more
/// invalid characters as defined by <see cref="System.IO.Path.InvalidPathChars" />.
/// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="filePath" /> is <see langword="null" />.
/// </exception>
/// <exception cref="PathTooLongException">
/// The specified path, file name, or both exceed the system-defined maximum length. For example, on
/// Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260
/// characters.
/// </exception>
/// <exception cref="DirectoryNotFoundException">
/// The specified path is invalid, (for example, it is on an unmapped drive).
/// </exception>
/// <exception cref="UnauthorizedAccessException">
/// <paramref name="filePath" /> specified a directory.-or- The caller does not have the required permission.
/// </exception>
/// <exception cref="FileNotFoundException">
/// The file specified in <paramref name="filePath" /> was not found.
/// </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="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, RequestOptions options) string filePath, string text, bool isTTS, Embed embed, RequestOptions options)
{ {
@@ -177,6 +195,7 @@ namespace Discord.Rest
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false); return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false);
} }
#endif #endif
/// <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, RequestOptions options) Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options)
{ {
@@ -249,6 +268,7 @@ namespace Discord.Rest


return user; return user;
} }
/// <exception cref="InvalidOperationException">Resolving permissions requires the parent guild to be downloaded.</exception>
public static IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, public static IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client,
ulong? fromUserId, int? limit, RequestOptions options) ulong? fromUserId, int? limit, RequestOptions options)
{ {


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

@@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace Discord.Rest namespace Discord.Rest
{ {
/// <summary> /// <summary>
/// Represents a REST channel that can send and receive messages.
/// Represents a REST-based channel that can send and receive messages.
/// </summary> /// </summary>
public interface IRestMessageChannel : IMessageChannel public interface IRestMessageChannel : IMessageChannel
{ {


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

@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Discord.Rest namespace Discord.Rest
{ {
/// <summary> /// <summary>
/// Represents a REST channel that is private to select recipients.
/// Represents a REST-based channel that is private to select recipients.
/// </summary> /// </summary>
public interface IRestPrivateChannel : IPrivateChannel public interface IRestPrivateChannel : IPrivateChannel
{ {


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

@@ -7,7 +7,7 @@ using Model = Discord.API.Channel;
namespace Discord.Rest namespace Discord.Rest
{ {
/// <summary> /// <summary>
/// Represents a REST category channel.
/// Represents a REST-based category channel.
/// </summary> /// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestCategoryChannel : RestGuildChannel, ICategoryChannel public class RestCategoryChannel : RestGuildChannel, ICategoryChannel


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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -8,12 +8,14 @@ namespace Discord.Rest
{ {
public class RestChannel : RestEntity<ulong>, IChannel, IUpdateable public class RestChannel : RestEntity<ulong>, IChannel, IUpdateable
{ {
/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);


internal RestChannel(BaseDiscordClient discord, ulong id) internal RestChannel(BaseDiscordClient discord, ulong id)
: base(discord, id) : base(discord, id)
{ {
} }
/// <exception cref="InvalidOperationException">Unexpected channel type.</exception>
internal static RestChannel Create(BaseDiscordClient discord, Model model) internal static RestChannel Create(BaseDiscordClient discord, Model model)
{ {
switch (model.Type) switch (model.Type)
@@ -28,6 +30,7 @@ namespace Discord.Rest
return new RestChannel(discord, model.Id); return new RestChannel(discord, model.Id);
} }
} }
/// <exception cref="InvalidOperationException">Unexpected channel type.</exception>
internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model) internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model)
{ {
switch (model.Type) switch (model.Type)
@@ -42,13 +45,17 @@ namespace Discord.Rest
} }
internal virtual void Update(Model model) { } internal virtual void Update(Model model) { }


/// <inheritdoc />
public virtual Task UpdateAsync(RequestOptions options = null) => Task.Delay(0); public virtual Task UpdateAsync(RequestOptions options = null) => Task.Delay(0);


//IChannel //IChannel
/// <inheritdoc />
string IChannel.Name => null; string IChannel.Name => null;


/// <inheritdoc />
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(null); //Overridden => Task.FromResult<IUser>(null); //Overridden
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden
} }


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

@@ -10,7 +10,7 @@ using Model = Discord.API.Channel;
namespace Discord.Rest namespace Discord.Rest
{ {
/// <summary> /// <summary>
/// Represents a REST DM channel.
/// Represents a REST-based DM channel.
/// </summary> /// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel
@@ -37,6 +37,7 @@ namespace Discord.Rest
Recipient.Update(model.Recipients.Value[0]); Recipient.Update(model.Recipients.Value[0]);
} }


/// <inheritdoc />
public override async Task UpdateAsync(RequestOptions options = null) public override async Task UpdateAsync(RequestOptions options = null)
{ {
var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false);
@@ -93,16 +94,20 @@ namespace Discord.Rest
public override string ToString() => $"@{Recipient}"; public override string ToString() => $"@{Recipient}";
private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)";


//IDMChannel
//IDMChannel
/// <inheritdoc />
IUser IDMChannel.Recipient => Recipient; IUser IDMChannel.Recipient => Recipient;


//IRestPrivateChannel //IRestPrivateChannel
/// <inheritdoc />
IReadOnlyCollection<RestUser> IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); IReadOnlyCollection<RestUser> IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient);


//IPrivateChannel //IPrivateChannel
/// <inheritdoc />
IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient); IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient);


//IMessageChannel //IMessageChannel
/// <inheritdoc />
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -110,6 +115,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -117,6 +123,7 @@ namespace Discord.Rest
else else
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>(); return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
} }
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -124,6 +131,7 @@ namespace Discord.Rest
else else
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>(); return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
} }
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -131,25 +139,33 @@ namespace Discord.Rest
else else
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>(); return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
} }
/// <inheritdoc />
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);


#if FILESYSTEM #if FILESYSTEM
/// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false);
#endif #endif
/// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
/// <inheritdoc />
IDisposable IMessageChannel.EnterTypingState(RequestOptions options) IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options); => EnterTypingState(options);


//IChannel //IChannel
/// <inheritdoc />
string IChannel.Name => $"@{Recipient}"; string IChannel.Name => $"@{Recipient}";


/// <inheritdoc />
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(id)); => Task.FromResult<IUser>(GetUser(id));
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable();
} }


+ 13
- 0
src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs View File

@@ -146,6 +146,7 @@ namespace Discord.Rest
public override string ToString() => Name; public override string ToString() => Name;


//IGuildChannel //IGuildChannel
/// <inheritdoc />
IGuild IGuildChannel.Guild IGuild IGuildChannel.Guild
{ {
get get
@@ -156,32 +157,44 @@ namespace Discord.Rest
} }
} }


/// <inheritdoc />
async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync(RequestOptions options) async Task<IReadOnlyCollection<IInviteMetadata>> IGuildChannel.GetInvitesAsync(RequestOptions options)
=> await GetInvitesAsync(options).ConfigureAwait(false); => await GetInvitesAsync(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) async Task<IInviteMetadata> IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
=> await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); => await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);


/// <inheritdoc />
OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role)
=> GetPermissionOverwrite(role); => GetPermissionOverwrite(role);
/// <inheritdoc />
OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user)
=> GetPermissionOverwrite(user); => GetPermissionOverwrite(user);
/// <inheritdoc />
async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options)
=> await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false);
/// <inheritdoc />
async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options)
=> await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false);
/// <inheritdoc />
async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options)
=> await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false);
/// <inheritdoc />
async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options)
=> await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false);


/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overridden //Overridden in Text/Voice => AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overridden //Overridden in Text/Voice
/// <inheritdoc />
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuildUser>(null); //Overridden in Text/Voice => Task.FromResult<IGuildUser>(null); //Overridden in Text/Voice


//IChannel //IChannel
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden in Text/Voice => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden in Text/Voice
/// <inheritdoc />
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(null); //Overridden in Text/Voice => Task.FromResult<IUser>(null); //Overridden in Text/Voice
} }


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

@@ -1,4 +1,4 @@
using Discord.API.Rest;
using Discord.API.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -14,10 +14,11 @@ namespace Discord.Rest
internal static class GuildHelper internal static class GuildHelper
{ {
//General //General
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public static async Task<Model> ModifyAsync(IGuild guild, BaseDiscordClient client, public static async Task<Model> ModifyAsync(IGuild guild, BaseDiscordClient client,
Action<GuildProperties> func, RequestOptions options) Action<GuildProperties> func, RequestOptions options)
{ {
if (func == null) throw new NullReferenceException(nameof(func));
if (func == null) throw new ArgumentNullException(nameof(func));


var args = new GuildProperties(); var args = new GuildProperties();
func(args); func(args);
@@ -62,10 +63,11 @@ namespace Discord.Rest


return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false);
} }
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, BaseDiscordClient client,
Action<GuildEmbedProperties> func, RequestOptions options) Action<GuildEmbedProperties> func, RequestOptions options)
{ {
if (func == null) throw new NullReferenceException(nameof(func));
if (func == null) throw new ArgumentNullException(nameof(func));


var args = new GuildEmbedProperties(); var args = new GuildEmbedProperties();
func(args); func(args);
@@ -139,6 +141,7 @@ namespace Discord.Rest
var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id, options).ConfigureAwait(false); var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id, options).ConfigureAwait(false);
return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray(); return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray();
} }
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, public static async Task<RestTextChannel> CreateTextChannelAsync(IGuild guild, BaseDiscordClient client,
string name, RequestOptions options) string name, RequestOptions options)
{ {
@@ -148,6 +151,7 @@ namespace Discord.Rest
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false);
return RestTextChannel.Create(client, guild, model); return RestTextChannel.Create(client, guild, model);
} }
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, public static async Task<RestVoiceChannel> CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client,
string name, RequestOptions options) string name, RequestOptions options)
{ {
@@ -157,6 +161,7 @@ namespace Discord.Rest
var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false);
return RestVoiceChannel.Create(client, guild, model); return RestVoiceChannel.Create(client, guild, model);
} }
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
public static async Task<RestCategoryChannel> CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, public static async Task<RestCategoryChannel> CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client,
string name, RequestOptions options) string name, RequestOptions options)
{ {
@@ -191,6 +196,7 @@ namespace Discord.Rest
} }


//Roles //Roles
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
public static async Task<RestRole> CreateRoleAsync(IGuild guild, BaseDiscordClient client, public static async Task<RestRole> CreateRoleAsync(IGuild guild, BaseDiscordClient client,
string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options)
{ {
@@ -292,11 +298,12 @@ namespace Discord.Rest
Image = image.ToModel() Image = image.ToModel()
}; };
if (roles.IsSpecified) if (roles.IsSpecified)
apiargs.RoleIds = roles.Value?.Select(xr => xr.Id)?.ToArray();
apiargs.RoleIds = roles.Value?.Select(xr => xr.Id).ToArray();


var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options).ConfigureAwait(false); var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options).ConfigureAwait(false);
return emote.ToEntity(); return emote.ToEntity();
} }
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</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)
{ {
@@ -310,7 +317,7 @@ namespace Discord.Rest
Name = props.Name Name = props.Name
}; };
if (props.Roles.IsSpecified) if (props.Roles.IsSpecified)
apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id)?.ToArray();
apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id).ToArray();


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();


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

@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Model = Discord.API.Ban; using Model = Discord.API.Ban;


namespace Discord.Rest namespace Discord.Rest
@@ -7,6 +7,7 @@ namespace Discord.Rest
public class RestBan : IBan public class RestBan : IBan
{ {
public RestUser User { get; } public RestUser User { get; }
/// <inheritdoc />
public string Reason { get; } public string Reason { get; }


internal RestBan(RestUser user, string reason) internal RestBan(RestUser user, string reason)
@@ -23,6 +24,7 @@ namespace Discord.Rest
private string DebuggerDisplay => $"{User}: {Reason}"; private string DebuggerDisplay => $"{User}: {Reason}";


//IBan //IBan
/// <inheritdoc />
IUser IBan.User => User; IUser IBan.User => User;
} }
} }

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

@@ -10,6 +10,9 @@ using Model = Discord.API.Guild;


namespace Discord.Rest namespace Discord.Rest
{ {
/// <summary>
/// Represents a REST-based guild/server.
/// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestGuild : RestEntity<ulong>, IGuild, IUpdateable public class RestGuild : RestEntity<ulong>, IGuild, IUpdateable
{ {
@@ -17,32 +20,50 @@ namespace Discord.Rest
private ImmutableArray<GuildEmote> _emotes; private ImmutableArray<GuildEmote> _emotes;
private ImmutableArray<string> _features; private ImmutableArray<string> _features;


/// <inheritdoc />
public string Name { get; private set; } public string Name { get; private set; }
/// <inheritdoc />
public int AFKTimeout { get; private set; } public int AFKTimeout { get; private set; }
/// <inheritdoc />
public bool IsEmbeddable { get; private set; } public bool IsEmbeddable { get; private set; }
/// <inheritdoc />
public VerificationLevel VerificationLevel { get; private set; } public VerificationLevel VerificationLevel { get; private set; }
/// <inheritdoc />
public MfaLevel MfaLevel { get; private set; } public MfaLevel MfaLevel { get; private set; }
/// <inheritdoc />
public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } public DefaultMessageNotifications DefaultMessageNotifications { get; private set; }


/// <inheritdoc />
public ulong? AFKChannelId { get; private set; } public ulong? AFKChannelId { get; private set; }
/// <inheritdoc />
public ulong? EmbedChannelId { get; private set; } public ulong? EmbedChannelId { get; private set; }
/// <inheritdoc />
public ulong? SystemChannelId { get; private set; } public ulong? SystemChannelId { get; private set; }
/// <inheritdoc />
public ulong OwnerId { get; private set; } public ulong OwnerId { get; private set; }
/// <inheritdoc />
public string VoiceRegionId { get; private set; } public string VoiceRegionId { get; private set; }
/// <inheritdoc />
public string IconId { get; private set; } public string IconId { get; private set; }
/// <inheritdoc />
public string SplashId { get; private set; } public string SplashId { get; private set; }
internal bool Available { get; private set; } internal bool Available { get; private set; }


/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);


[Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")] [Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")]
public ulong DefaultChannelId => Id; public ulong DefaultChannelId => Id;
/// <inheritdoc />
public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); public string IconUrl => CDN.GetGuildIconUrl(Id, IconId);
/// <inheritdoc />
public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId);


public RestRole EveryoneRole => GetRole(Id); public RestRole EveryoneRole => GetRole(Id);
public IReadOnlyCollection<RestRole> Roles => _roles.ToReadOnlyCollection(); public IReadOnlyCollection<RestRole> Roles => _roles.ToReadOnlyCollection();
/// <inheritdoc />
public IReadOnlyCollection<GuildEmote> Emotes => _emotes; public IReadOnlyCollection<GuildEmote> Emotes => _emotes;
/// <inheritdoc />
public IReadOnlyCollection<string> Features => _features; public IReadOnlyCollection<string> Features => _features;


internal RestGuild(BaseDiscordClient client, ulong id) internal RestGuild(BaseDiscordClient client, ulong id)
@@ -103,37 +124,48 @@ namespace Discord.Rest
} }


//General //General
/// <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, options).ConfigureAwait(false));
/// <inheritdoc />
public Task DeleteAsync(RequestOptions options = null) public Task DeleteAsync(RequestOptions options = null)
=> GuildHelper.DeleteAsync(this, Discord, options); => GuildHelper.DeleteAsync(this, Discord, options);


/// <inheritdoc />
/// <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);
Update(model); Update(model);
} }

/// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
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);
Update(model); 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();
await GuildHelper.ReorderChannelsAsync(this, Discord, arr, options).ConfigureAwait(false); await GuildHelper.ReorderChannelsAsync(this, Discord, arr, options).ConfigureAwait(false);
} }
/// <inheritdoc />
public async Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null) public async Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null)
{ {
var models = await GuildHelper.ReorderRolesAsync(this, Discord, args, options).ConfigureAwait(false); var models = await GuildHelper.ReorderRolesAsync(this, Discord, args, options).ConfigureAwait(false);
foreach (var model in models) foreach (var model in models)
{ {
var role = GetRole(model.Id); var role = GetRole(model.Id);
if (role != null)
role.Update(model);
role?.Update(model);
} }
} }

/// <inheritdoc />
public Task LeaveAsync(RequestOptions options = null) public Task LeaveAsync(RequestOptions options = null)
=> GuildHelper.LeaveAsync(this, Discord, options); => GuildHelper.LeaveAsync(this, Discord, options);


@@ -141,13 +173,17 @@ namespace Discord.Rest
public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null)
=> GuildHelper.GetBansAsync(this, Discord, options); => GuildHelper.GetBansAsync(this, Discord, options);


/// <inheritdoc />
public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null)
=> GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options);
/// <inheritdoc />
public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null)
=> GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options);


/// <inheritdoc />
public Task RemoveBanAsync(IUser user, RequestOptions options = null) public Task RemoveBanAsync(IUser user, RequestOptions options = null)
=> GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options);
/// <inheritdoc />
public Task RemoveBanAsync(ulong userId, RequestOptions options = null) public Task RemoveBanAsync(ulong userId, RequestOptions options = null)
=> GuildHelper.RemoveBanAsync(this, Discord, userId, options); => GuildHelper.RemoveBanAsync(this, Discord, userId, options);


@@ -261,6 +297,7 @@ namespace Discord.Rest
public Task<RestGuildUser> GetOwnerAsync(RequestOptions options = null) public Task<RestGuildUser> GetOwnerAsync(RequestOptions options = null)
=> GuildHelper.GetUserAsync(this, Discord, OwnerId, options); => GuildHelper.GetUserAsync(this, Discord, OwnerId, options);


/// <inheritdoc />
public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null)
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options);


@@ -270,28 +307,45 @@ namespace Discord.Rest
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
=> GuildHelper.GetWebhooksAsync(this, Discord, options); => GuildHelper.GetWebhooksAsync(this, Discord, options);


/// <summary>
/// Returns the name of the guild.
/// </summary>
/// <returns>
/// The name of the guild.
/// </returns>
public override string ToString() => Name; public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id})"; private string DebuggerDisplay => $"{Name} ({Id})";


//Emotes //Emotes
/// <inheritdoc />
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetEmoteAsync(this, Discord, id, options); => GuildHelper.GetEmoteAsync(this, Discord, id, options);
/// <inheritdoc />
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 />
/// <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 />
public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null)
=> GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options);


//IGuild //IGuild
/// <inheritdoc />
bool IGuild.Available => Available; bool IGuild.Available => Available;
/// <inheritdoc />
IAudioClient IGuild.AudioClient => null; IAudioClient IGuild.AudioClient => null;
/// <inheritdoc />
IRole IGuild.EveryoneRole => EveryoneRole; IRole IGuild.EveryoneRole => EveryoneRole;
/// <inheritdoc />
IReadOnlyCollection<IRole> IGuild.Roles => Roles; IReadOnlyCollection<IRole> IGuild.Roles => Roles;


/// <inheritdoc />
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options)
=> await GetBansAsync(options).ConfigureAwait(false); => await GetBansAsync(options).ConfigureAwait(false);


/// <inheritdoc />
async Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -299,6 +353,7 @@ namespace Discord.Rest
else else
return ImmutableArray.Create<IGuildChannel>(); return ImmutableArray.Create<IGuildChannel>();
} }
/// <inheritdoc />
async Task<IGuildChannel> IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IGuildChannel> IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -306,6 +361,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IReadOnlyCollection<ITextChannel>> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<ITextChannel>> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -313,6 +369,7 @@ namespace Discord.Rest
else else
return ImmutableArray.Create<ITextChannel>(); return ImmutableArray.Create<ITextChannel>();
} }
/// <inheritdoc />
async Task<ITextChannel> IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) async Task<ITextChannel> IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -320,6 +377,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -327,6 +385,7 @@ namespace Discord.Rest
else else
return ImmutableArray.Create<IVoiceChannel>(); return ImmutableArray.Create<IVoiceChannel>();
} }
/// <inheritdoc />
async Task<IReadOnlyCollection<ICategoryChannel>> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<ICategoryChannel>> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -334,6 +393,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -341,6 +401,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IVoiceChannel> IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) async Task<IVoiceChannel> IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -348,6 +409,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<ITextChannel> IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) async Task<ITextChannel> IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -355,6 +417,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
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)
@@ -362,6 +425,7 @@ namespace Discord.Rest
else else
return null; 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)
@@ -369,26 +433,35 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name, RequestOptions options) async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name, RequestOptions options)
=> await CreateTextChannelAsync(name, options).ConfigureAwait(false); => await CreateTextChannelAsync(name, options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name, RequestOptions options)
=> await CreateVoiceChannelAsync(name, options).ConfigureAwait(false); => await CreateVoiceChannelAsync(name, options).ConfigureAwait(false);
/// <inheritdoc />
async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, RequestOptions options) async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, RequestOptions options)
=> await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false);


/// <inheritdoc />
async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync(RequestOptions options) async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync(RequestOptions options)
=> await GetIntegrationsAsync(options).ConfigureAwait(false); => await GetIntegrationsAsync(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IGuildIntegration> IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) async Task<IGuildIntegration> IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options)
=> await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false);


/// <inheritdoc />
async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync(RequestOptions options) async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync(RequestOptions options)
=> await GetInvitesAsync(options).ConfigureAwait(false); => await GetInvitesAsync(options).ConfigureAwait(false);


/// <inheritdoc />
IRole IGuild.GetRole(ulong id) IRole IGuild.GetRole(ulong id)
=> GetRole(id); => GetRole(id);
/// <inheritdoc />
async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options)
=> await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false);


/// <inheritdoc />
async Task<IGuildUser> IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IGuildUser> IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -396,6 +469,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IGuildUser> IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) async Task<IGuildUser> IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -403,6 +477,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IGuildUser> IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) async Task<IGuildUser> IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -410,6 +485,7 @@ namespace Discord.Rest
else else
return null; return null;
} }
/// <inheritdoc />
async Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -418,12 +494,14 @@ namespace Discord.Rest
return ImmutableArray.Create<IGuildUser>(); return ImmutableArray.Create<IGuildUser>();
} }
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Downloading users is not supported with a REST-based guild.</exception>
/// <exception cref="NotSupportedException">Downloading users is not supported for a REST-based guild.</exception>
Task IGuild.DownloadUsersAsync() => Task IGuild.DownloadUsersAsync() =>
throw new NotSupportedException(); throw new NotSupportedException();


/// <inheritdoc />
async Task<IWebhook> IGuild.GetWebhookAsync(ulong id, RequestOptions options) async Task<IWebhook> IGuild.GetWebhookAsync(ulong id, RequestOptions options)
=> await GetWebhookAsync(id, options).ConfigureAwait(false); => await GetWebhookAsync(id, options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options) async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false); => await GetWebhooksAsync(options).ConfigureAwait(false);
} }


+ 2
- 2
src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs View File

@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Model = Discord.API.GuildEmbed; using Model = Discord.API.GuildEmbed;


namespace Discord namespace Discord
@@ -19,7 +19,7 @@ namespace Discord
return new RestGuildEmbed(model.Enabled, model.ChannelId); return new RestGuildEmbed(model.Enabled, model.ChannelId);
} }


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

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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Integration; using Model = Discord.API.Integration;
@@ -10,18 +10,28 @@ namespace Discord.Rest
{ {
private long _syncedAtTicks; private long _syncedAtTicks;


/// <inheritdoc />
public string Name { get; private set; } public string Name { get; private set; }
/// <inheritdoc />
public string Type { get; private set; } public string Type { get; private set; }
/// <inheritdoc />
public bool IsEnabled { get; private set; } public bool IsEnabled { get; private set; }
/// <inheritdoc />
public bool IsSyncing { get; private set; } public bool IsSyncing { get; private set; }
/// <inheritdoc />
public ulong ExpireBehavior { get; private set; } public ulong ExpireBehavior { get; private set; }
/// <inheritdoc />
public ulong ExpireGracePeriod { get; private set; } public ulong ExpireGracePeriod { get; private set; }
/// <inheritdoc />
public ulong GuildId { get; private set; } public ulong GuildId { get; private set; }
/// <inheritdoc />
public ulong RoleId { get; private set; } public ulong RoleId { get; private set; }
public RestUser User { get; private set; } public RestUser User { get; private set; }
/// <inheritdoc />
public IntegrationAccount Account { get; private set; } public IntegrationAccount Account { get; private set; }
internal IGuild Guild { get; private set; } internal IGuild Guild { get; private set; }


/// <inheritdoc />
public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks);


internal RestGuildIntegration(BaseDiscordClient discord, IGuild guild, ulong id) internal RestGuildIntegration(BaseDiscordClient discord, IGuild guild, ulong id)
@@ -78,6 +88,7 @@ namespace Discord.Rest
public override string ToString() => Name; public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})";


/// <inheritdoc />
IGuild IGuildIntegration.Guild IGuild IGuildIntegration.Guild
{ {
get get
@@ -87,6 +98,7 @@ namespace Discord.Rest
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
} }
} }
/// <inheritdoc />
IUser IGuildIntegration.User => User; IUser IGuildIntegration.User => User;
} }
} }

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

@@ -9,12 +9,17 @@ namespace Discord.Rest
public class RestUserGuild : RestEntity<ulong>, IUserGuild public class RestUserGuild : RestEntity<ulong>, IUserGuild
{ {
private string _iconId; private string _iconId;

/// <inheritdoc />
public string Name { get; private set; } public string Name { get; private set; }
/// <inheritdoc />
public bool IsOwner { get; private set; } public bool IsOwner { get; private set; }
/// <inheritdoc />
public GuildPermissions Permissions { get; private set; } public GuildPermissions Permissions { get; private set; }


/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
/// <inheritdoc />
public string IconUrl => CDN.GetGuildIconUrl(Id, _iconId); public string IconUrl => CDN.GetGuildIconUrl(Id, _iconId);


internal RestUserGuild(BaseDiscordClient discord, ulong id) internal RestUserGuild(BaseDiscordClient discord, ulong id)
@@ -40,6 +45,7 @@ namespace Discord.Rest
{ {
await Discord.ApiClient.LeaveGuildAsync(Id, options).ConfigureAwait(false); await Discord.ApiClient.LeaveGuildAsync(Id, options).ConfigureAwait(false);
} }
/// <inheritdoc />
public async Task DeleteAsync(RequestOptions options = null) public async Task DeleteAsync(RequestOptions options = null)
{ {
await Discord.ApiClient.DeleteGuildAsync(Id, options).ConfigureAwait(false); await Discord.ApiClient.DeleteGuildAsync(Id, options).ConfigureAwait(false);


+ 3
- 1
src/Discord.Net.Rest/Entities/Invites/RestInvite.cs View File

@@ -59,7 +59,8 @@ namespace Discord.Rest


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

/// <inheritdoc />
IGuild IInvite.Guild IGuild IInvite.Guild
{ {
get get
@@ -71,6 +72,7 @@ namespace Discord.Rest
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
} }
} }
/// <inheritdoc />
IChannel IInvite.Channel IChannel IInvite.Channel
{ {
get get


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

@@ -10,11 +10,13 @@ namespace Discord.Rest
{ {
internal static class MessageHelper internal static class MessageHelper
{ {
/// <exception cref="InvalidOperationException">Only the author of a message may modify the message.</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<Model> ModifyAsync(IMessage msg, BaseDiscordClient client, Action<MessageProperties> func, public static async Task<Model> ModifyAsync(IMessage msg, BaseDiscordClient client, Action<MessageProperties> func,
RequestOptions options) RequestOptions options)
{ {
if (msg.Author.Id != client.CurrentUser.Id) if (msg.Author.Id != client.CurrentUser.Id)
throw new InvalidOperationException("Only the author of a message may change it.");
throw new InvalidOperationException("Only the author of a message may modify the message.");


var args = new MessageProperties(); var args = new MessageProperties();
func(args); func(args);


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

@@ -35,6 +35,7 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>();
public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>(); public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>();
/// <inheritdoc />
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();


/// <inheritdoc /> /// <inheritdoc />
@@ -75,10 +76,14 @@ namespace Discord.Rest


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


/// <inheritdoc />
MessageType IMessage.Type => MessageType.Default; MessageType IMessage.Type => MessageType.Default;
IUser IMessage.Author => Author; IUser IMessage.Author => Author;
/// <inheritdoc />
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments;
/// <inheritdoc />
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds;
/// <inheritdoc />
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray();
} }
} }

+ 21
- 4
src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs View File

@@ -17,23 +17,33 @@ namespace Discord.Rest
private ImmutableArray<Embed> _embeds; private ImmutableArray<Embed> _embeds;
private ImmutableArray<ITag> _tags; private ImmutableArray<ITag> _tags;
private ImmutableArray<RestReaction> _reactions; private ImmutableArray<RestReaction> _reactions;

/// <inheritdoc />
public override bool IsTTS => _isTTS; public override bool IsTTS => _isTTS;
/// <inheritdoc />
public override bool IsPinned => _isPinned; public override bool IsPinned => _isPinned;
/// <inheritdoc />
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks);
/// <inheritdoc />
public override IReadOnlyCollection<Attachment> Attachments => _attachments; public override IReadOnlyCollection<Attachment> Attachments => _attachments;
/// <inheritdoc />
public override IReadOnlyCollection<Embed> Embeds => _embeds; public override IReadOnlyCollection<Embed> Embeds => _embeds;
/// <inheritdoc />
public override IReadOnlyCollection<ulong> MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); public override IReadOnlyCollection<ulong> MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags);
/// <inheritdoc />
public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags);
/// <inheritdoc />
public override IReadOnlyCollection<RestUser> MentionedUsers => MessageHelper.FilterTagsByValue<RestUser>(TagType.UserMention, _tags); public override IReadOnlyCollection<RestUser> MentionedUsers => MessageHelper.FilterTagsByValue<RestUser>(TagType.UserMention, _tags);
/// <inheritdoc />
public override IReadOnlyCollection<ITag> Tags => _tags; public override IReadOnlyCollection<ITag> Tags => _tags;
/// <inheritdoc />
public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me });


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)
{ {
} }
internal static new RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model)
internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model)
{ {
var entity = new RestUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); var entity = new RestUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model));
entity.Update(model); entity.Update(model);
@@ -124,30 +134,37 @@ namespace Discord.Rest
} }
} }


/// <inheritdoc />
public async Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null) public async Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null)
{ {
var model = await MessageHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); var model = await MessageHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false);
Update(model); Update(model);
} }


/// <inheritdoc />
public Task AddReactionAsync(IEmote emote, RequestOptions options = null) public Task AddReactionAsync(IEmote emote, RequestOptions options = null)
=> MessageHelper.AddReactionAsync(this, emote, Discord, options); => MessageHelper.AddReactionAsync(this, emote, Discord, options);
/// <inheritdoc />
public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null)
=> MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options);
/// <inheritdoc />
public Task RemoveAllReactionsAsync(RequestOptions options = null) public Task RemoveAllReactionsAsync(RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsAsync(this, Discord, options); => MessageHelper.RemoveAllReactionsAsync(this, Discord, options);
/// <inheritdoc />
public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null)
=> MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create<ulong>(); }, Discord, options); => MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create<ulong>(); }, Discord, options);
/// <inheritdoc />
public Task PinAsync(RequestOptions options = null) public Task PinAsync(RequestOptions options = null)
=> MessageHelper.PinAsync(this, Discord, options); => MessageHelper.PinAsync(this, Discord, options);
/// <inheritdoc />
public Task UnpinAsync(RequestOptions options = null) public Task UnpinAsync(RequestOptions options = null)
=> MessageHelper.UnpinAsync(this, Discord, options); => MessageHelper.UnpinAsync(this, Discord, options);


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


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

@@ -19,6 +19,7 @@ namespace Discord.Rest
public string Description { get; private set; } public string Description { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public string[] RPCOrigins { get; private set; } public string[] RPCOrigins { get; private set; }
/// <inheritdoc />
public ulong Flags { get; private set; } public ulong Flags { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
@@ -52,6 +53,7 @@ namespace Discord.Rest
Owner = RestUser.Create(Discord, model.Owner.Value); Owner = RestUser.Create(Discord, model.Owner.Value);
} }


/// <exception cref="InvalidOperationException">Unable to update this object from a different application token.</exception>
public async Task UpdateAsync() public async Task UpdateAsync()
{ {
var response = await Discord.ApiClient.GetMyApplicationAsync().ConfigureAwait(false); var response = await Discord.ApiClient.GetMyApplicationAsync().ConfigureAwait(false);
@@ -60,6 +62,12 @@ namespace Discord.Rest
Update(response); Update(response);
} }


/// <summary>
/// Gets the name of the application.
/// </summary>
/// <returns>
/// Name of the application.
/// </returns>
public override string ToString() => Name; public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id})"; private string DebuggerDisplay => $"{Name} ({Id})";
} }


+ 14
- 1
src/Discord.Net.Rest/Entities/Roles/RestRole.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Role; using Model = Discord.API.Role;
@@ -9,16 +9,25 @@ namespace Discord.Rest
public class RestRole : RestEntity<ulong>, IRole public class RestRole : RestEntity<ulong>, IRole
{ {
internal IGuild Guild { get; } internal IGuild Guild { get; }
/// <inheritdoc />
public Color Color { get; private set; } public Color Color { get; private set; }
/// <inheritdoc />
public bool IsHoisted { get; private set; } public bool IsHoisted { get; private set; }
/// <inheritdoc />
public bool IsManaged { get; private set; } public bool IsManaged { get; private set; }
/// <inheritdoc />
public bool IsMentionable { get; private set; } public bool IsMentionable { get; private set; }
/// <inheritdoc />
public string Name { get; private set; } public string Name { get; private set; }
/// <inheritdoc />
public GuildPermissions Permissions { get; private set; } public GuildPermissions Permissions { get; private set; }
/// <inheritdoc />
public int Position { get; private set; } public int Position { get; private set; }


/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
public bool IsEveryone => Id == Guild.Id; public bool IsEveryone => Id == Guild.Id;
/// <inheritdoc />
public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id);


internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id)
@@ -43,20 +52,24 @@ namespace Discord.Rest
Permissions = new GuildPermissions(model.Permissions); Permissions = new GuildPermissions(model.Permissions);
} }


/// <inheritdoc />
public async Task ModifyAsync(Action<RoleProperties> func, RequestOptions options = null) public async Task ModifyAsync(Action<RoleProperties> func, RequestOptions options = null)
{ {
var model = await RoleHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); var model = await RoleHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false);
Update(model); Update(model);
} }
/// <inheritdoc />
public Task DeleteAsync(RequestOptions options = null) public Task DeleteAsync(RequestOptions options = null)
=> RoleHelper.DeleteAsync(this, Discord, options); => RoleHelper.DeleteAsync(this, Discord, options);


/// <inheritdoc />
public int CompareTo(IRole role) => RoleUtils.Compare(this, role); public int CompareTo(IRole role) => RoleUtils.Compare(this, role);


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


//IRole //IRole
/// <inheritdoc />
IGuild IRole.Guild IGuild IRole.Guild
{ {
get get


+ 12
- 1
src/Discord.Net.Rest/Entities/Users/RestConnection.cs View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using Model = Discord.API.Connection; using Model = Discord.API.Connection;
@@ -8,10 +8,15 @@ namespace Discord
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestConnection : IConnection public class RestConnection : IConnection
{ {
/// <inheritdoc />
public string Id { get; } public string Id { get; }
/// <inheritdoc />
public string Type { get; } public string Type { get; }
/// <inheritdoc />
public string Name { get; } public string Name { get; }
/// <inheritdoc />
public bool IsRevoked { get; } public bool IsRevoked { get; }
/// <inheritdoc />
public IReadOnlyCollection<ulong> IntegrationIds { get; } public IReadOnlyCollection<ulong> IntegrationIds { get; }


internal RestConnection(string id, string type, string name, bool isRevoked, IReadOnlyCollection<ulong> integrationIds) internal RestConnection(string id, string type, string name, bool isRevoked, IReadOnlyCollection<ulong> integrationIds)
@@ -28,6 +33,12 @@ namespace Discord
return new RestConnection(model.Id, model.Type, model.Name, model.Revoked, model.Integrations.ToImmutableArray()); return new RestConnection(model.Id, model.Type, model.Name, model.Revoked, model.Integrations.ToImmutableArray());
} }


/// <summary>
/// Gets the name of the connection.
/// </summary>
/// <returns>
/// Name of the connection.
/// </returns>
public override string ToString() => Name; public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked ? ", Revoked" : "")})"; private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked ? ", Revoked" : "")})";
} }


+ 9
- 0
src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs View File

@@ -24,7 +24,9 @@ namespace Discord.Rest


/// <inheritdoc /> /// <inheritdoc />
public ulong GuildId => Guild.Id; public ulong GuildId => Guild.Id;

/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="InvalidOperationException" accessor="get">Resolving permissions requires the parent guild to be downloaded.</exception>
public GuildPermissions GuildPermissions public GuildPermissions GuildPermissions
{ {
get get
@@ -112,6 +114,7 @@ namespace Discord.Rest
=> UserHelper.RemoveRolesAsync(this, Discord, roles, options); => UserHelper.RemoveRolesAsync(this, Discord, roles, options);


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="InvalidOperationException">Resolving permissions requires the parent guild to be downloaded.</exception>
public ChannelPermissions GetPermissions(IGuildChannel channel) public ChannelPermissions GetPermissions(IGuildChannel channel)
{ {
var guildPerms = GuildPermissions; var guildPerms = GuildPermissions;
@@ -119,6 +122,7 @@ namespace Discord.Rest
} }


//IGuildUser //IGuildUser
/// <inheritdoc />
IGuild IGuildUser.Guild IGuild IGuildUser.Guild
{ {
get get
@@ -130,10 +134,15 @@ namespace Discord.Rest
} }


//IVoiceState //IVoiceState
/// <inheritdoc />
bool IVoiceState.IsSelfDeafened => false; bool IVoiceState.IsSelfDeafened => false;
/// <inheritdoc />
bool IVoiceState.IsSelfMuted => false; bool IVoiceState.IsSelfMuted => false;
/// <inheritdoc />
bool IVoiceState.IsSuppressed => false; bool IVoiceState.IsSuppressed => false;
/// <inheritdoc />
IVoiceChannel IVoiceState.VoiceChannel => null; IVoiceChannel IVoiceState.VoiceChannel => null;
/// <inheritdoc />
string IVoiceState.VoiceSessionId => null; string IVoiceState.VoiceSessionId => null;
} }
} }

+ 9
- 1
src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.User; using Model = Discord.API.User;
@@ -8,8 +8,11 @@ namespace Discord.Rest
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestSelfUser : RestUser, ISelfUser public class RestSelfUser : RestUser, ISelfUser
{ {
/// <inheritdoc />
public string Email { get; private set; } public string Email { get; private set; }
/// <inheritdoc />
public bool IsVerified { get; private set; } public bool IsVerified { get; private set; }
/// <inheritdoc />
public bool IsMfaEnabled { get; private set; } public bool IsMfaEnabled { get; private set; }


internal RestSelfUser(BaseDiscordClient discord, ulong id) internal RestSelfUser(BaseDiscordClient discord, ulong id)
@@ -22,6 +25,7 @@ namespace Discord.Rest
entity.Update(model); entity.Update(model);
return entity; return entity;
} }
/// <inheritdoc />
internal override void Update(Model model) internal override void Update(Model model)
{ {
base.Update(model); base.Update(model);
@@ -34,6 +38,8 @@ namespace Discord.Rest
IsMfaEnabled = model.MfaEnabled.Value; IsMfaEnabled = model.MfaEnabled.Value;
} }


/// <inheritdoc />
/// <exception cref="InvalidOperationException">Unable to update this object using a different token.</exception>
public override async Task UpdateAsync(RequestOptions options = null) public override async Task UpdateAsync(RequestOptions options = null)
{ {
var model = await Discord.ApiClient.GetMyUserAsync(options).ConfigureAwait(false); var model = await Discord.ApiClient.GetMyUserAsync(options).ConfigureAwait(false);
@@ -42,6 +48,8 @@ namespace Discord.Rest
Update(model); Update(model);
} }


/// <inheritdoc />
/// <exception cref="InvalidOperationException">Unable to modify this object using a different token.</exception>
public async Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null) public async Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null)
{ {
if (Id != Discord.CurrentUser.Id) if (Id != Discord.CurrentUser.Id)


+ 14
- 0
src/Discord.Net.Rest/Entities/Users/RestUser.cs View File

@@ -8,16 +8,26 @@ namespace Discord.Rest
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestUser : RestEntity<ulong>, IUser, IUpdateable public class RestUser : RestEntity<ulong>, IUser, IUpdateable
{ {
/// <inheritdoc />
public bool IsBot { get; private set; } public bool IsBot { get; private set; }
/// <inheritdoc />
public string Username { get; private set; } public string Username { get; private set; }
/// <inheritdoc />
public ushort DiscriminatorValue { get; private set; } public ushort DiscriminatorValue { get; private set; }
/// <inheritdoc />
public string AvatarId { get; private set; } public string AvatarId { get; private set; }


/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
/// <inheritdoc />
public string Discriminator => DiscriminatorValue.ToString("D4"); public string Discriminator => DiscriminatorValue.ToString("D4");
/// <inheritdoc />
public string Mention => MentionUtils.MentionUser(Id); public string Mention => MentionUtils.MentionUser(Id);
/// <inheritdoc />
public virtual IActivity Activity => null; public virtual IActivity Activity => null;
/// <inheritdoc />
public virtual UserStatus Status => UserStatus.Offline; public virtual UserStatus Status => UserStatus.Offline;
/// <inheritdoc />
public virtual bool IsWebhook => false; public virtual bool IsWebhook => false;


internal RestUser(BaseDiscordClient discord, ulong id) internal RestUser(BaseDiscordClient discord, ulong id)
@@ -48,6 +58,7 @@ namespace Discord.Rest
Username = model.Username.Value; Username = model.Username.Value;
} }


/// <inheritdoc />
public virtual async Task UpdateAsync(RequestOptions options = null) public virtual async Task UpdateAsync(RequestOptions options = null)
{ {
var model = await Discord.ApiClient.GetUserAsync(Id, options).ConfigureAwait(false); var model = await Discord.ApiClient.GetUserAsync(Id, options).ConfigureAwait(false);
@@ -57,9 +68,11 @@ namespace Discord.Rest
public Task<RestDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null) public Task<RestDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null)
=> UserHelper.CreateDMChannelAsync(this, Discord, options); => UserHelper.CreateDMChannelAsync(this, Discord, options);


/// <inheritdoc />
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); => CDN.GetUserAvatarUrl(Id, AvatarId, size, format);


/// <inheritdoc />
public string GetDefaultAvatarUrl() public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);


@@ -67,6 +80,7 @@ namespace Discord.Rest
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})";


//IUser //IUser
/// <inheritdoc />
async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options)
=> await GetOrCreateDMChannelAsync(options).ConfigureAwait(false); => await GetOrCreateDMChannelAsync(options).ConfigureAwait(false);
} }


+ 14
- 1
src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Webhook; using Model = Discord.API.Webhook;
@@ -11,14 +11,21 @@ namespace Discord.Rest
internal IGuild Guild { get; private set; } internal IGuild Guild { get; private set; }
internal ITextChannel Channel { get; private set; } internal ITextChannel Channel { get; private set; }


/// <inheritdoc />
public ulong ChannelId { get; } public ulong ChannelId { get; }
/// <inheritdoc />
public string Token { get; } public string Token { get; }


/// <inheritdoc />
public string Name { get; private set; } public string Name { get; private set; }
/// <inheritdoc />
public string AvatarId { get; private set; } public string AvatarId { get; private set; }
/// <inheritdoc />
public ulong? GuildId { get; private set; } public ulong? GuildId { get; private set; }
/// <inheritdoc />
public IUser Creator { get; private set; } public IUser Creator { get; private set; }


/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);


internal RestWebhook(BaseDiscordClient discord, IGuild guild, ulong id, string token, ulong channelId) internal RestWebhook(BaseDiscordClient discord, IGuild guild, ulong id, string token, ulong channelId)
@@ -59,12 +66,14 @@ namespace Discord.Rest
Name = model.Name.Value; Name = model.Name.Value;
} }


/// <inheritdoc />
public async Task UpdateAsync(RequestOptions options = null) public async Task UpdateAsync(RequestOptions options = null)
{ {
var model = await Discord.ApiClient.GetWebhookAsync(Id, options).ConfigureAwait(false); var model = await Discord.ApiClient.GetWebhookAsync(Id, options).ConfigureAwait(false);
Update(model); Update(model);
} }


/// <inheritdoc />
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); => CDN.GetUserAvatarUrl(Id, AvatarId, size, format);


@@ -74,6 +83,7 @@ namespace Discord.Rest
Update(model); Update(model);
} }


/// <inheritdoc />
public Task DeleteAsync(RequestOptions options = null) public Task DeleteAsync(RequestOptions options = null)
=> WebhookHelper.DeleteAsync(this, Discord, options); => WebhookHelper.DeleteAsync(this, Discord, options);


@@ -81,10 +91,13 @@ namespace Discord.Rest
private string DebuggerDisplay => $"Webhook: {Name} ({Id})"; private string DebuggerDisplay => $"Webhook: {Name} ({Id})";


//IWebhook //IWebhook
/// <inheritdoc />
IGuild IWebhook.Guild IGuild IWebhook.Guild
=> Guild ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); => Guild ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
/// <inheritdoc />
ITextChannel IWebhook.Channel ITextChannel IWebhook.Channel
=> Channel ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); => Channel ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
/// <inheritdoc />
Task IWebhook.ModifyAsync(Action<WebhookProperties> func, RequestOptions options) Task IWebhook.ModifyAsync(Action<WebhookProperties> func, RequestOptions options)
=> ModifyAsync(func, options); => ModifyAsync(func, options);
} }


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

@@ -82,6 +82,8 @@ namespace Discord.Net.Rest
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
} }
} }

/// <exception cref="InvalidOperationException">Unsupported param type.</exception>
public async Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null) public async Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null)
{ {
string uri = Path.Combine(_baseUrl, endpoint); string uri = Path.Combine(_baseUrl, endpoint);
@@ -111,7 +113,7 @@ namespace Discord.Net.Rest
content.Add(new StreamContent(stream), p.Key, fileValue.Filename); content.Add(new StreamContent(stream), p.Key, fileValue.Filename);
continue; continue;
} }
default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\"");
default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\".");
} }
} }
} }


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

@@ -6,6 +6,7 @@ namespace Discord.Net.Rest
{ {
public static readonly RestClientProvider Instance = Create(); public static readonly RestClientProvider Instance = Create();


/// <exception cref="PlatformNotSupportedException">The default RestClientProvider is not supported on this platform.</exception>
public static RestClientProvider Create(bool useProxy = false) public static RestClientProvider Create(bool useProxy = false)
{ {
return url => return url =>


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

@@ -126,7 +126,7 @@ namespace Discord.Net.Queue
if ((request.Options.RetryMode & RetryMode.RetryTimeouts) == 0) if ((request.Options.RetryMode & RetryMode.RetryTimeouts) == 0)
throw; throw;


await Task.Delay(500);
await Task.Delay(500).ConfigureAwait(false);
continue; //Retry continue; //Retry
} }
/*catch (Exception) /*catch (Exception)


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

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


namespace Discord.Net namespace Discord.Net
@@ -15,7 +15,7 @@ namespace Discord.Net
internal RateLimitInfo(Dictionary<string, string> headers) internal RateLimitInfo(Dictionary<string, string> headers)
{ {
IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) && IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) &&
bool.TryParse(temp, out var isGlobal) ? isGlobal : false;
bool.TryParse(temp, out var isGlobal) && isGlobal;
Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) &&
int.TryParse(temp, out var limit) ? limit : (int?)null; int.TryParse(temp, out var limit) ? limit : (int?)null;
Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) &&


+ 6
- 3
src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs View File

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


@@ -22,19 +22,22 @@ namespace Discord.Audio.Streams
_decoder = new OpusDecoder(); _decoder = new OpusDecoder();
} }


/// <exception cref="InvalidOperationException">Header received with no payload.</exception>
public override void WriteHeader(ushort seq, uint timestamp, bool missed) public override void WriteHeader(ushort seq, uint timestamp, bool missed)
{ {
if (_hasHeader) if (_hasHeader)
throw new InvalidOperationException("Header received with no payload");
throw new InvalidOperationException("Header received with no payload.");
_hasHeader = true; _hasHeader = true;


_nextMissed = missed; _nextMissed = missed;
_next.WriteHeader(seq, timestamp, missed); _next.WriteHeader(seq, timestamp, missed);
} }

/// <exception cref="InvalidOperationException">Received payload without an RTP header.</exception>
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken)
{ {
if (!_hasHeader) if (!_hasHeader)
throw new InvalidOperationException("Received payload without an RTP header");
throw new InvalidOperationException("Received payload without an RTP header.");
_hasHeader = false; _hasHeader = false;


if (!_nextMissed) if (!_nextMissed)


+ 4
- 1
src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs View File

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


namespace Discord.Audio.Streams namespace Discord.Audio.Streams
@@ -20,6 +21,8 @@ namespace Discord.Audio.Streams
_nonce = new byte[24]; _nonce = new byte[24];
} }


/// <exception cref="OperationCanceledException">The token has had cancellation requested.</exception>
/// <exception cref="ObjectDisposedException">The associated <see cref="T:System.Threading.CancellationTokenSource" /> has been disposed.</exception>
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken)
{ {
cancelToken.ThrowIfCancellationRequested(); cancelToken.ThrowIfCancellationRequested();


+ 7
- 3
src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs View File

@@ -20,21 +20,25 @@ namespace Discord.Audio.Streams
_client = (AudioClient)client; _client = (AudioClient)client;
_nonce = new byte[24]; _nonce = new byte[24];
} }

/// <exception cref="InvalidOperationException">Header received with no payload.</exception>
public override void WriteHeader(ushort seq, uint timestamp, bool missed) public override void WriteHeader(ushort seq, uint timestamp, bool missed)
{ {
if (_hasHeader) if (_hasHeader)
throw new InvalidOperationException("Header received with no payload");
throw new InvalidOperationException("Header received with no payload.");


_nextSeq = seq; _nextSeq = seq;
_nextTimestamp = timestamp; _nextTimestamp = timestamp;
_hasHeader = true; _hasHeader = true;
} }
/// <exception cref="InvalidOperationException">Received payload without an RTP header.</exception>
/// <exception cref="OperationCanceledException">The token has had cancellation requested.</exception>
/// <exception cref="ObjectDisposedException">The associated <see cref="T:System.Threading.CancellationTokenSource" /> has been disposed.</exception>
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken)
{ {
cancelToken.ThrowIfCancellationRequested(); cancelToken.ThrowIfCancellationRequested();
if (!_hasHeader) if (!_hasHeader)
throw new InvalidOperationException("Received payload without an RTP header");
throw new InvalidOperationException("Received payload without an RTP header.");
_hasHeader = false; _hasHeader = false;


if (_client.SecretKey == null) if (_client.SecretKey == null)


+ 1
- 1
src/Discord.Net.WebSocket/BaseSocketClient.Events.cs View File

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


namespace Discord.WebSocket namespace Discord.WebSocket


+ 137
- 5
src/Discord.Net.WebSocket/BaseSocketClient.cs View File

@@ -10,42 +10,174 @@ namespace Discord.WebSocket
{ {
protected readonly DiscordSocketConfig BaseConfig; protected readonly DiscordSocketConfig BaseConfig;


/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary>
/// <summary>
/// Gets the estimated round-trip latency, in milliseconds, to the gateway server.
/// </summary>
public abstract int Latency { get; protected set; } public abstract int Latency { get; protected set; }
public abstract UserStatus Status { get; protected set; }
/// <summary>
/// Gets the status for the logged-in user.
/// </summary>
public abstract UserStatus Status { get; protected set; }
/// <summary>
/// Gets the activity for the logged-in user.
/// </summary>
public abstract IActivity Activity { get; protected set; } public abstract IActivity Activity { get; protected set; }


internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;


/// <summary>
/// Gets the current logged-in user.
/// </summary>
public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; }
/// <summary>
/// Gets a collection of guilds that the logged-in user is currently in.
/// </summary>
public abstract IReadOnlyCollection<SocketGuild> Guilds { get; } public abstract IReadOnlyCollection<SocketGuild> Guilds { get; }
/// <summary>
/// Gets a collection of private channels that are currently open for the logged-in user.
/// </summary>
public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; } public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; }
/// <summary>
/// Gets a collection of available voice regions for the logged-in user.
/// </summary>
public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; } public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; }


internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client)
: base(config, client) => BaseConfig = config; : base(config, client) => BaseConfig = config;
private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
=> new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent);

/// <summary>
/// Gets a Discord application information for the logged-in user.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// Application information. This reflects your application information you submitted when creating a
/// Discord application via the Developer Portal.
/// </returns>
public abstract Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null); public abstract Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null);
/// <summary>
/// Gets a user who shares a mutual guild with logged-in user with the provided snowflake ID.
/// </summary>
/// <param name="id">The user snowflake ID.</param>
/// <returns>
/// A user who shares a mutual guild with the logged-in user and who is also present in the WebSocket cache;
/// or <see langword="null"/> when the user cannot be found.
/// </returns>
public abstract SocketUser GetUser(ulong id); public abstract SocketUser GetUser(ulong id);

/// <summary>
/// Gets a user who shares a mutual guild with the logged-in user with the provided username and discriminator value combo.
/// </summary>
/// <param name="username">The name of the user.</param>
/// <param name="discriminator">The discriminator value of the user.</param>
/// <returns>
/// A user who shares a mutual guild with the logged-in user and who is also present in the WebSocket cache;
/// or <see langword="null"/> when the user cannot be found.
/// </returns>
public abstract SocketUser GetUser(string username, string discriminator); public abstract SocketUser GetUser(string username, string discriminator);
/// <summary>
/// Gets a channel that the logged-in user is accessible to with the provided ID.
/// </summary>
/// <param name="id">The channel snowflake ID.</param>
/// <returns>
/// A generic channel object (voice, text, category, etc.); or <see langword="null" /> when the channel
/// cannot be found.
/// </returns>
public abstract SocketChannel GetChannel(ulong id); public abstract SocketChannel GetChannel(ulong id);
/// <summary>
/// Gets a guild that the logged-in user is accessible to with the provided ID.
/// </summary>
/// <param name="id">The guild snowflake ID.</param>
/// <returns>
/// A guild; or <see langword="null"/> when the guild cannot be found.
/// </returns>
public abstract SocketGuild GetGuild(ulong id); public abstract SocketGuild GetGuild(ulong id);
/// <summary>
/// Gets a voice region with the provided ID.
/// </summary>
/// <param name="id">The unique identifier of the voice region.</param>
/// <returns>
/// A voice region; or <see langword="null"/> if none can be found.
/// </returns>
public abstract RestVoiceRegion GetVoiceRegion(string id); public abstract RestVoiceRegion GetVoiceRegion(string id);
/// <inheritdoc /> /// <inheritdoc />
public abstract Task StartAsync(); public abstract Task StartAsync();
/// <inheritdoc /> /// <inheritdoc />
public abstract Task StopAsync(); public abstract Task StopAsync();
/// <summary>
/// Sets the current status of the logged-in user (e.g. Online, Do not Disturb).
/// </summary>
/// <param name="status">The new status to be set.</param>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
public abstract Task SetStatusAsync(UserStatus status); public abstract Task SetStatusAsync(UserStatus status);
/// <summary>
/// Sets the game of the logged-in user.
/// </summary>
/// <param name="name">The name of the game.</param>
/// <param name="streamUrl">If streaming, the URL of the stream. Must be a valid Twitch URL.</param>
/// <param name="type">The type of the game.</param>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
public abstract Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing); public abstract Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing);
/// <summary>
/// Sets the <paramref name="activity"/> of the logged-in user.
/// </summary>
/// <remarks>
/// This method sets the <paramref name="activity"/> of the user. Please note that Rich Presence cannot be
/// set via this method or client. Rich Presence is strictly limited to RPC clients only. Furthermore,
/// Discord will only accept setting of name and the type of activity.
/// </remarks>
/// <param name="activity">The activty to be set.</param>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
public abstract Task SetActivityAsync(IActivity activity); public abstract Task SetActivityAsync(IActivity activity);
public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds);
/// <summary>
/// Attempts to download users into the user cache for the selected guilds.
/// </summary>
/// <param name="guilds">The guilds to download the members from.</param>
/// <returns>
/// An awaitable <see cref="Task"/>.
/// </returns>
public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds);

/// <summary>
/// Creates a guild for the logged-in user who is in less than 10 active guilds.
/// </summary>
/// <remarks>
/// This method creates a new guild on behalf of the logged-in user. Note that due to Discord's limitation,
/// this method will only work for users that are in less than 10 guilds.
/// </remarks>
/// <param name="name">The name of the new guild.</param>
/// <param name="region">The voice region to create the guild with.</param>
/// <param name="jpegIcon">The icon of the guild.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing the newly created guild.
/// </returns>
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 ?? RequestOptions.Default); => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default);
/// <summary>
/// Gets the connections that the logged-in user has set up.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing a collection of connections.
/// </returns>
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null)
=> ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default);
/// <summary>
/// Gets an invite with the provided invite identifier.
/// </summary>
/// <param name="inviteId">The invitation identifier.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// An awaitable <see cref="Task"/> containing the invite information.
/// </returns>
public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null)
=> ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default);


+ 3
- 3
src/Discord.Net.WebSocket/ClientState.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -72,9 +72,9 @@ namespace Discord.WebSocket
switch (channel) switch (channel)
{ {
case SocketDMChannel dmChannel: case SocketDMChannel dmChannel:
_dmChannels.TryRemove(dmChannel.Recipient.Id, out var ignored);
_dmChannels.TryRemove(dmChannel.Recipient.Id, out var _);
break; break;
case SocketGroupChannel groupChannel:
case SocketGroupChannel _:
_groupChannels.TryRemove(id); _groupChannels.TryRemove(id);
break; break;
} }


+ 3
- 1
src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs View File

@@ -2,7 +2,9 @@ using Discord.WebSocket;


namespace Discord.Commands namespace Discord.Commands
{ {
/// <summary> The WebSocket variant of <see cref="ICommandContext"/>, which may contain the client, user, guild, channel, and message. </summary>
/// <summary>
/// Represents a WebSocket-based context of a command. This may include the client, guild, channel, user, and message.
/// </summary>
public class SocketCommandContext : ICommandContext public class SocketCommandContext : ICommandContext
{ {
/// <summary> /// <summary>


+ 6
- 3
src/Discord.Net.WebSocket/DiscordSocketApiClient.cs View File

@@ -120,12 +120,14 @@ namespace Discord.API
} }
finally { _stateLock.Release(); } finally { _stateLock.Release(); }
} }
/// <exception cref="InvalidOperationException">The client must be logged in before connecting.</exception>
/// <exception cref="NotSupportedException">This client is not configured with WebSocket support.</exception>
internal override async Task ConnectInternalAsync() internal override async Task ConnectInternalAsync()
{ {
if (LoginState != LoginState.LoggedIn) if (LoginState != LoginState.LoggedIn)
throw new InvalidOperationException("You must log in before connecting.");
throw new InvalidOperationException("The client must be logged in before connecting.");
if (WebSocketClient == null) if (WebSocketClient == null)
throw new NotSupportedException("This client is not configured with websocket support.");
throw new NotSupportedException("This client is not configured with WebSocket support.");


//Re-create streams to reset the zlib state //Re-create streams to reset the zlib state
_compressed?.Dispose(); _compressed?.Dispose();
@@ -176,10 +178,11 @@ namespace Discord.API
} }
finally { _stateLock.Release(); } finally { _stateLock.Release(); }
} }
/// <exception cref="NotSupportedException">This client is not configured with WebSocket support.</exception>
internal override async Task DisconnectInternalAsync() internal override async Task DisconnectInternalAsync()
{ {
if (WebSocketClient == null) if (WebSocketClient == null)
throw new NotSupportedException("This client is not configured with websocket support.");
throw new NotSupportedException("This client is not configured with WebSocket support.");


if (ConnectionState == ConnectionState.Disconnected) return; if (ConnectionState == ConnectionState.Disconnected) return;
ConnectionState = ConnectionState.Disconnecting; ConnectionState = ConnectionState.Disconnecting;


+ 37
- 7
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -46,7 +46,9 @@ namespace Discord.WebSocket
public ConnectionState ConnectionState => _connection.State; public ConnectionState ConnectionState => _connection.State;
/// <inheritdoc /> /// <inheritdoc />
public override int Latency { get; protected set; } public override int Latency { get; protected set; }
/// <inheritdoc />
public override UserStatus Status { get; protected set; } = UserStatus.Online; public override UserStatus Status { get; protected set; } = UserStatus.Online;
/// <inheritdoc />
public override IActivity Activity { get; protected set; } public override IActivity Activity { get; protected set; }


//From DiscordSocketConfig //From DiscordSocketConfig
@@ -58,14 +60,17 @@ namespace Discord.WebSocket
internal WebSocketProvider WebSocketProvider { get; private set; } internal WebSocketProvider WebSocketProvider { get; private set; }
internal bool AlwaysDownloadUsers { get; private set; } internal bool AlwaysDownloadUsers { get; private set; }
internal int? HandlerTimeout { get; private set; } internal int? HandlerTimeout { get; private set; }
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
/// <inheritdoc />
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds;
/// <inheritdoc />
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels; public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels;
public IReadOnlyCollection<SocketDMChannel> DMChannels public IReadOnlyCollection<SocketDMChannel> DMChannels
=> State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray();
public IReadOnlyCollection<SocketGroupChannel> GroupChannels public IReadOnlyCollection<SocketGroupChannel> GroupChannels
=> State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray();
/// <inheritdoc />
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection();


/// <summary> Creates a new REST/WebSocket Discord client. </summary> /// <summary> Creates a new REST/WebSocket Discord client. </summary>
@@ -128,6 +133,7 @@ namespace Discord.WebSocket
} }
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost);
/// <inheritdoc />
internal override void Dispose(bool disposing) internal override void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
@@ -136,7 +142,8 @@ namespace Discord.WebSocket
ApiClient.Dispose(); ApiClient.Dispose();
} }
} }

/// <inheritdoc />
internal override async Task OnLoginAsync(TokenType tokenType, string token) internal override async Task OnLoginAsync(TokenType tokenType, string token)
{ {
if (_parentClient == null) if (_parentClient == null)
@@ -147,6 +154,7 @@ namespace Discord.WebSocket
else else
_voiceRegions = _parentClient._voiceRegions; _voiceRegions = _parentClient._voiceRegions;
} }
/// <inheritdoc />
internal override async Task OnLogoutAsync() internal override async Task OnLogoutAsync()
{ {
await StopAsync().ConfigureAwait(false); await StopAsync().ConfigureAwait(false);
@@ -154,8 +162,10 @@ namespace Discord.WebSocket
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>();
} }


/// <inheritdoc />
public override async Task StartAsync() public override async Task StartAsync()
=> await _connection.StartAsync().ConfigureAwait(false); => await _connection.StartAsync().ConfigureAwait(false);
/// <inheritdoc />
public override async Task StopAsync() public override async Task StopAsync()
=> await _connection.StopAsync().ConfigureAwait(false); => await _connection.StopAsync().ConfigureAwait(false);
@@ -277,7 +287,7 @@ namespace Discord.WebSocket
return null; return null;
} }


/// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary>
/// <inheritdoc />
public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds)
{ {
if (ConnectionState == ConnectionState.Connected) if (ConnectionState == ConnectionState.Connected)
@@ -316,6 +326,7 @@ namespace Discord.WebSocket
} }
} }


/// <inheritdoc />
public override async Task SetStatusAsync(UserStatus status) public override async Task SetStatusAsync(UserStatus status)
{ {
Status = status; Status = status;
@@ -325,6 +336,7 @@ namespace Discord.WebSocket
_statusSince = null; _statusSince = null;
await SendStatusAsync().ConfigureAwait(false); await SendStatusAsync().ConfigureAwait(false);
} }
/// <inheritdoc />
public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing)
{ {
if (!string.IsNullOrEmpty(streamUrl)) if (!string.IsNullOrEmpty(streamUrl))
@@ -335,6 +347,7 @@ namespace Discord.WebSocket
Activity = null; Activity = null;
await SendStatusAsync().ConfigureAwait(false); await SendStatusAsync().ConfigureAwait(false);
} }
/// <inheritdoc />
public override async Task SetActivityAsync(IActivity activity) public override async Task SetActivityAsync(IActivity activity)
{ {
Activity = activity; Activity = activity;
@@ -351,8 +364,8 @@ namespace Discord.WebSocket


var gameModel = new GameModel(); var gameModel = new GameModel();
// Discord only accepts rich presence over RPC, don't even bother building a payload // Discord only accepts rich presence over RPC, don't even bother building a payload
if (Activity is RichGame game)
throw new NotSupportedException("Outgoing Rich Presences are not supported");
if (Activity is RichGame)
throw new NotSupportedException("Outgoing Rich Presences are not supported via WebSocket.");


if (Activity != null) if (Activity != null)
{ {
@@ -479,7 +492,7 @@ namespace Discord.WebSocket
await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false);
await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false);
}); });
var _ = _connection.CompleteAsync();
_ = _connection.CompleteAsync();
} }
break; break;
case "RESUMED": case "RESUMED":
@@ -1173,7 +1186,7 @@ namespace Discord.WebSocket


var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id);
bool isCached = msg != null; bool isCached = msg != null;
var cacheable = new Cacheable<IMessage, ulong>(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id));
var cacheable = new Cacheable<IMessage, ulong>(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false));


await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false);
} }
@@ -1609,6 +1622,7 @@ namespace Discord.WebSocket
return guild; return guild;
} }


/// <exception cref="InvalidOperationException">Unexpected channel type is created.</exception>
internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state) internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state)
{ {
var channel = SocketChannel.CreatePrivate(this, state, model); var channel = SocketChannel.CreatePrivate(this, state, model);
@@ -1781,43 +1795,59 @@ namespace Discord.WebSocket
internal int GetAudioId() => _nextAudioId++; internal int GetAudioId() => _nextAudioId++;


//IDiscordClient //IDiscordClient
/// <inheritdoc />
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
=> await GetApplicationInfoAsync().ConfigureAwait(false); => await GetApplicationInfoAsync().ConfigureAwait(false);


/// <inheritdoc />
Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IChannel>(GetChannel(id)); => Task.FromResult<IChannel>(GetChannel(id));
/// <inheritdoc />
Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels);
/// <inheritdoc />
Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) Task<IReadOnlyCollection<IDMChannel>> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IDMChannel>>(DMChannels); => Task.FromResult<IReadOnlyCollection<IDMChannel>>(DMChannels);
/// <inheritdoc />
Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) Task<IReadOnlyCollection<IGroupChannel>> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IGroupChannel>>(GroupChannels); => Task.FromResult<IReadOnlyCollection<IGroupChannel>>(GroupChannels);


/// <inheritdoc />
async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options)
=> await GetConnectionsAsync().ConfigureAwait(false); => await GetConnectionsAsync().ConfigureAwait(false);


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


/// <inheritdoc />
Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuild>(GetGuild(id)); => Task.FromResult<IGuild>(GetGuild(id));
/// <inheritdoc />
Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds);
/// <inheritdoc />
async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options)
=> await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false);


/// <inheritdoc />
Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(id)); => Task.FromResult<IUser>(GetUser(id));
/// <inheritdoc />
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options)
=> Task.FromResult<IUser>(GetUser(username, discriminator)); => Task.FromResult<IUser>(GetUser(username, discriminator));


/// <inheritdoc />
Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions);
/// <inheritdoc />
Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
=> Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id));


/// <inheritdoc />
async Task IDiscordClient.StartAsync() async Task IDiscordClient.StartAsync()
=> await StartAsync().ConfigureAwait(false); => await StartAsync().ConfigureAwait(false);
/// <inheritdoc />
async Task IDiscordClient.StopAsync() async Task IDiscordClient.StopAsync()
=> await StopAsync().ConfigureAwait(false); => await StopAsync().ConfigureAwait(false);
} }


+ 1
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs View File

@@ -58,6 +58,7 @@ namespace Discord.WebSocket
internal abstract SocketUser GetUserInternal(ulong id); internal abstract SocketUser GetUserInternal(ulong id);
internal abstract IReadOnlyCollection<SocketUser> GetUsersInternal(); internal abstract IReadOnlyCollection<SocketUser> GetUsersInternal();


private string DebuggerDisplay => $"Unknown ({Id}, Channel)";
internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; internal SocketChannel Clone() => MemberwiseClone() as SocketChannel;


//IChannel //IChannel


+ 5
- 4
src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs View File

@@ -1,4 +1,4 @@
using Discord.Rest;
using Discord.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -57,7 +57,7 @@ namespace Discord.WebSocket
else else
return ImmutableArray.Create<SocketMessage>(); return ImmutableArray.Create<SocketMessage>();
} }
/// <exception cref="NotSupportedException">Unexpected <see cref="ISocketMessageChannel"/> type.</exception>
public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord,
SocketMessage msg) SocketMessage msg)
{ {
@@ -66,9 +66,10 @@ namespace Discord.WebSocket
case SocketDMChannel dmChannel: dmChannel.AddMessage(msg); break; case SocketDMChannel dmChannel: dmChannel.AddMessage(msg); break;
case SocketGroupChannel groupChannel: groupChannel.AddMessage(msg); break; case SocketGroupChannel groupChannel: groupChannel.AddMessage(msg); break;
case SocketTextChannel textChannel: textChannel.AddMessage(msg); break; case SocketTextChannel textChannel: textChannel.AddMessage(msg); break;
default: throw new NotSupportedException("Unexpected ISocketMessageChannel type");
default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type.");
} }
} }
/// <exception cref="NotSupportedException">Unexpected <see cref="ISocketMessageChannel"/> type.</exception>
public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord, public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord,
ulong id) ulong id)
{ {
@@ -77,7 +78,7 @@ namespace Discord.WebSocket
case SocketDMChannel dmChannel: return dmChannel.RemoveMessage(id); case SocketDMChannel dmChannel: return dmChannel.RemoveMessage(id);
case SocketGroupChannel groupChannel: return groupChannel.RemoveMessage(id); case SocketGroupChannel groupChannel: return groupChannel.RemoveMessage(id);
case SocketTextChannel textChannel: return textChannel.RemoveMessage(id); case SocketTextChannel textChannel: return textChannel.RemoveMessage(id);
default: throw new NotSupportedException("Unexpected ISocketMessageChannel type");
default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type.");
} }
} }
} }


+ 0
- 1
src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs View File

@@ -111,7 +111,6 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);
/// <inheritdoc />
public IDisposable EnterTypingState(RequestOptions options = null) public IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options); => ChannelHelper.EnterTypingState(this, Discord, options);




+ 14
- 2
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -357,9 +357,12 @@ namespace Discord.WebSocket
=> GuildHelper.DeleteAsync(this, Discord, options); => GuildHelper.DeleteAsync(this, Discord, options);


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null) public Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null)
=> GuildHelper.ModifyAsync(this, Discord, func, options); => GuildHelper.ModifyAsync(this, Discord, func, options);

/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null) public Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null)
=> GuildHelper.ModifyEmbedAsync(this, Discord, func, options); => GuildHelper.ModifyEmbedAsync(this, Discord, func, options);
/// <inheritdoc /> /// <inheritdoc />
@@ -431,31 +434,37 @@ namespace Discord.WebSocket
/// </returns> /// </returns>
public SocketVoiceChannel GetVoiceChannel(ulong id) public SocketVoiceChannel GetVoiceChannel(ulong id)
=> GetChannel(id) as SocketVoiceChannel; => GetChannel(id) as SocketVoiceChannel;

/// <summary> /// <summary>
/// Creates a text channel with the provided name. /// Creates a text channel with the provided name.
/// </summary> /// </summary>
/// <param name="name">The name of the new channel.</param> /// <param name="name">The name of the new channel.</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 <see langword="null" />.</exception>
/// <returns> /// <returns>
/// The created text channel. /// The created text channel.
/// </returns> /// </returns>
public Task<RestTextChannel> CreateTextChannelAsync(string name, RequestOptions options = null) public Task<RestTextChannel> CreateTextChannelAsync(string name, RequestOptions options = null)
=> GuildHelper.CreateTextChannelAsync(this, Discord, name, options); => GuildHelper.CreateTextChannelAsync(this, Discord, name, options);

/// <summary> /// <summary>
/// Creates a voice channel with the provided name. /// Creates a voice channel with the provided name.
/// </summary> /// </summary>
/// <param name="name">The name of the new channel.</param> /// <param name="name">The name of the new channel.</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 <see langword="null" />.</exception>
/// <returns> /// <returns>
/// The created voice channel. /// The created voice channel.
/// </returns> /// </returns>
public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null) public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name, RequestOptions options = null)
=> GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options);

/// <summary> /// <summary>
/// Creates a category channel with the provided name. /// Creates a category channel with the provided name.
/// </summary> /// </summary>
/// <param name="name">The name of the new channel.</param> /// <param name="name">The name of the new channel.</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 <see langword="null" />.</exception>
/// <returns> /// <returns>
/// The created category channel. /// The created category channel.
/// </returns> /// </returns>
@@ -507,6 +516,7 @@ namespace Discord.WebSocket
return value; return value;
return null; return null;
} }

/// <summary> /// <summary>
/// Creates a role. /// Creates a role.
/// </summary> /// </summary>
@@ -517,6 +527,7 @@ namespace Discord.WebSocket
/// <param name="color">The color of the role. Set to <see langword="null" /> to use the default color.</param> /// <param name="color">The color of the role. Set to <see langword="null" /> to use the default color.</param>
/// <param name="isHoisted">Used to determine if users of this role are separated in the user list.</param> /// <param name="isHoisted">Used to determine if users of this role are separated in the user list.</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 <see langword="null" />.</exception>
/// <returns> /// <returns>
/// The created role. /// The created role.
/// </returns> /// </returns>
@@ -645,6 +656,7 @@ namespace Discord.WebSocket
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 <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 />
@@ -931,9 +943,9 @@ namespace Discord.WebSocket


/// <inheritdoc /> /// <inheritdoc />
async Task<IWebhook> IGuild.GetWebhookAsync(ulong id, RequestOptions options) async Task<IWebhook> IGuild.GetWebhookAsync(ulong id, RequestOptions options)
=> await GetWebhookAsync(id, options);
=> await GetWebhookAsync(id, options).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options) async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options);
=> await GetWebhooksAsync(options).ConfigureAwait(false);
} }
} }

+ 21
- 1
src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs View File

@@ -1,4 +1,4 @@
using Discord.Rest;
using Discord.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -8,27 +8,38 @@ using Model = Discord.API.Message;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
/// <summary>
/// Represents a WebSocket-based message.
/// </summary>
public abstract class SocketMessage : SocketEntity<ulong>, IMessage public abstract class SocketMessage : SocketEntity<ulong>, IMessage
{ {
private long _timestampTicks; private long _timestampTicks;
public SocketUser Author { get; } public SocketUser Author { get; }
public ISocketMessageChannel Channel { get; } public ISocketMessageChannel Channel { get; }
/// <inheritdoc />
public MessageSource Source { get; } public MessageSource Source { get; }


/// <inheritdoc />
public string Content { get; private set; } public string Content { get; private set; }


/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
/// <inheritdoc />
public virtual bool IsTTS => false; public virtual bool IsTTS => false;
/// <inheritdoc />
public virtual bool IsPinned => false; public virtual bool IsPinned => false;
/// <inheritdoc />
public virtual DateTimeOffset? EditedTimestamp => null; public virtual DateTimeOffset? EditedTimestamp => null;
public virtual IReadOnlyCollection<Attachment> Attachments => ImmutableArray.Create<Attachment>(); public virtual IReadOnlyCollection<Attachment> Attachments => ImmutableArray.Create<Attachment>();
public virtual IReadOnlyCollection<Embed> Embeds => ImmutableArray.Create<Embed>(); public virtual IReadOnlyCollection<Embed> Embeds => ImmutableArray.Create<Embed>();
public virtual IReadOnlyCollection<SocketGuildChannel> MentionedChannels => ImmutableArray.Create<SocketGuildChannel>(); public virtual IReadOnlyCollection<SocketGuildChannel> MentionedChannels => ImmutableArray.Create<SocketGuildChannel>();
public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>(); public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>();
public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>(); public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>();
/// <inheritdoc />
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();


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


internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source)
@@ -54,6 +65,7 @@ namespace Discord.WebSocket
Content = model.Content.Value; Content = model.Content.Value;
} }


/// <inheritdoc />
public Task DeleteAsync(RequestOptions options = null) public Task DeleteAsync(RequestOptions options = null)
=> MessageHelper.DeleteAsync(this, Discord, options); => MessageHelper.DeleteAsync(this, Discord, options);


@@ -61,13 +73,21 @@ namespace Discord.WebSocket
internal SocketMessage Clone() => MemberwiseClone() as SocketMessage; internal SocketMessage Clone() => MemberwiseClone() as SocketMessage;


//IMessage //IMessage
/// <inheritdoc />
IUser IMessage.Author => Author; IUser IMessage.Author => Author;
/// <inheritdoc />
IMessageChannel IMessage.Channel => Channel; IMessageChannel IMessage.Channel => Channel;
/// <inheritdoc />
MessageType IMessage.Type => MessageType.Default; MessageType IMessage.Type => MessageType.Default;
/// <inheritdoc />
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments;
/// <inheritdoc />
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds;
/// <inheritdoc />
IReadOnlyCollection<ulong> IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); IReadOnlyCollection<ulong> IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray();
/// <inheritdoc />
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray();
/// <inheritdoc />
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray();
} }
} }

+ 4
- 1
src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs View File

@@ -1,4 +1,4 @@
using Model = Discord.API.Gateway.Reaction;
using Model = Discord.API.Gateway.Reaction;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
@@ -9,6 +9,7 @@ namespace Discord.WebSocket
public ulong MessageId { get; } public ulong MessageId { get; }
public Optional<SocketUserMessage> Message { get; } public Optional<SocketUserMessage> Message { get; }
public ISocketMessageChannel Channel { get; } public ISocketMessageChannel Channel { get; }
/// <inheritdoc />
public IEmote Emote { get; } public IEmote Emote { get; }


internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional<SocketUserMessage> message, ulong userId, Optional<IUser> user, IEmote emoji) internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional<SocketUserMessage> message, ulong userId, Optional<IUser> user, IEmote emoji)
@@ -30,6 +31,7 @@ namespace Discord.WebSocket
return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote); return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote);
} }


/// <inheritdoc />
public override bool Equals(object other) public override bool Equals(object other)
{ {
if (other == null) return false; if (other == null) return false;
@@ -41,6 +43,7 @@ namespace Discord.WebSocket
return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote);
} }


/// <inheritdoc />
public override int GetHashCode() public override int GetHashCode()
{ {
unchecked unchecked


+ 2
- 1
src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs View File

@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Model = Discord.API.Message; using Model = Discord.API.Message;


namespace Discord.WebSocket namespace Discord.WebSocket
@@ -6,6 +6,7 @@ namespace Discord.WebSocket
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketSystemMessage : SocketMessage, ISystemMessage public class SocketSystemMessage : SocketMessage, ISystemMessage
{ {
/// <inheritdoc />
public MessageType Type { get; private set; } public MessageType Type { get; private set; }


internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author)


+ 24
- 4
src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs View File

@@ -17,24 +17,34 @@ namespace Discord.WebSocket
private ImmutableArray<Attachment> _attachments; private ImmutableArray<Attachment> _attachments;
private ImmutableArray<Embed> _embeds; private ImmutableArray<Embed> _embeds;
private ImmutableArray<ITag> _tags; private ImmutableArray<ITag> _tags;
private List<SocketReaction> _reactions = new List<SocketReaction>();
private readonly List<SocketReaction> _reactions = new List<SocketReaction>();

/// <inheritdoc />
public override bool IsTTS => _isTTS; public override bool IsTTS => _isTTS;
/// <inheritdoc />
public override bool IsPinned => _isPinned; public override bool IsPinned => _isPinned;
/// <inheritdoc />
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks);
/// <inheritdoc />
public override IReadOnlyCollection<Attachment> Attachments => _attachments; public override IReadOnlyCollection<Attachment> Attachments => _attachments;
/// <inheritdoc />
public override IReadOnlyCollection<Embed> Embeds => _embeds; public override IReadOnlyCollection<Embed> Embeds => _embeds;
/// <inheritdoc />
public override IReadOnlyCollection<ITag> Tags => _tags; public override IReadOnlyCollection<ITag> Tags => _tags;
/// <inheritdoc />
public override IReadOnlyCollection<SocketGuildChannel> MentionedChannels => MessageHelper.FilterTagsByValue<SocketGuildChannel>(TagType.ChannelMention, _tags); public override IReadOnlyCollection<SocketGuildChannel> MentionedChannels => MessageHelper.FilterTagsByValue<SocketGuildChannel>(TagType.ChannelMention, _tags);
/// <inheritdoc />
public override IReadOnlyCollection<SocketRole> MentionedRoles => MessageHelper.FilterTagsByValue<SocketRole>(TagType.RoleMention, _tags); public override IReadOnlyCollection<SocketRole> MentionedRoles => MessageHelper.FilterTagsByValue<SocketRole>(TagType.RoleMention, _tags);
/// <inheritdoc />
public override IReadOnlyCollection<SocketUser> MentionedUsers => MessageHelper.FilterTagsByValue<SocketUser>(TagType.UserMention, _tags); public override IReadOnlyCollection<SocketUser> MentionedUsers => MessageHelper.FilterTagsByValue<SocketUser>(TagType.UserMention, _tags);
/// <inheritdoc />
public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) });


internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source)
: base(discord, id, channel, author, source) : base(discord, id, channel, author, source)
{ {
} }
internal static new SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model)
internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model)
{ {
var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model));
entity.Update(state, model); entity.Update(state, model);
@@ -121,30 +131,40 @@ namespace Discord.WebSocket
_reactions.Clear(); _reactions.Clear();
} }


/// <inheritdoc />
/// <exception cref="InvalidOperationException">Only the author of a message may modify the message.</exception>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null) public Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null)
=> MessageHelper.ModifyAsync(this, Discord, func, options); => MessageHelper.ModifyAsync(this, Discord, func, options);


/// <inheritdoc />
public Task AddReactionAsync(IEmote emote, RequestOptions options = null) public Task AddReactionAsync(IEmote emote, RequestOptions options = null)
=> MessageHelper.AddReactionAsync(this, emote, Discord, options); => MessageHelper.AddReactionAsync(this, emote, Discord, options);
/// <inheritdoc />
public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null)
=> MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options);
/// <inheritdoc />
public Task RemoveAllReactionsAsync(RequestOptions options = null) public Task RemoveAllReactionsAsync(RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsAsync(this, Discord, options); => MessageHelper.RemoveAllReactionsAsync(this, Discord, options);
/// <inheritdoc />
public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null)
=> MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create<ulong>(); }, Discord, options); => MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create<ulong>(); }, Discord, options);


/// <inheritdoc />
public Task PinAsync(RequestOptions options = null) public Task PinAsync(RequestOptions options = null)
=> MessageHelper.PinAsync(this, Discord, options); => MessageHelper.PinAsync(this, Discord, options);
/// <inheritdoc />
public Task UnpinAsync(RequestOptions options = null) public Task UnpinAsync(RequestOptions options = null)
=> MessageHelper.UnpinAsync(this, Discord, options); => MessageHelper.UnpinAsync(this, Discord, options);


public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
=> MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
/// <inheritdoc />
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})";
internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage; internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage;
} }


+ 14
- 1
src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs View File

@@ -1,4 +1,4 @@
using Discord.Rest;
using Discord.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -13,16 +13,25 @@ namespace Discord.WebSocket
{ {
public SocketGuild Guild { get; } public SocketGuild Guild { get; }


/// <inheritdoc />
public Color Color { get; private set; } public Color Color { get; private set; }
/// <inheritdoc />
public bool IsHoisted { get; private set; } public bool IsHoisted { get; private set; }
/// <inheritdoc />
public bool IsManaged { get; private set; } public bool IsManaged { get; private set; }
/// <inheritdoc />
public bool IsMentionable { get; private set; } public bool IsMentionable { get; private set; }
/// <inheritdoc />
public string Name { get; private set; } public string Name { get; private set; }
/// <inheritdoc />
public GuildPermissions Permissions { get; private set; } public GuildPermissions Permissions { get; private set; }
/// <inheritdoc />
public int Position { get; private set; } public int Position { get; private set; }


/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
public bool IsEveryone => Id == Guild.Id; public bool IsEveryone => Id == Guild.Id;
/// <inheritdoc />
public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id);
public IEnumerable<SocketGuildUser> Members public IEnumerable<SocketGuildUser> Members
=> Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); => Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id));
@@ -49,8 +58,10 @@ namespace Discord.WebSocket
Permissions = new GuildPermissions(model.Permissions); Permissions = new GuildPermissions(model.Permissions);
} }


/// <inheritdoc />
public Task ModifyAsync(Action<RoleProperties> func, RequestOptions options = null) public Task ModifyAsync(Action<RoleProperties> func, RequestOptions options = null)
=> RoleHelper.ModifyAsync(this, Discord, func, options); => RoleHelper.ModifyAsync(this, Discord, func, options);
/// <inheritdoc />
public Task DeleteAsync(RequestOptions options = null) public Task DeleteAsync(RequestOptions options = null)
=> RoleHelper.DeleteAsync(this, Discord, options); => RoleHelper.DeleteAsync(this, Discord, options);


@@ -58,9 +69,11 @@ namespace Discord.WebSocket
private string DebuggerDisplay => $"{Name} ({Id})"; private string DebuggerDisplay => $"{Name} ({Id})";
internal SocketRole Clone() => MemberwiseClone() as SocketRole; internal SocketRole Clone() => MemberwiseClone() as SocketRole;


/// <inheritdoc />
public int CompareTo(IRole role) => RoleUtils.Compare(this, role); public int CompareTo(IRole role) => RoleUtils.Compare(this, role);


//IRole //IRole
/// <inheritdoc />
IGuild IRole.Guild => Guild; IGuild IRole.Guild => Guild;
} }
} }

+ 1
- 1
src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs View File

@@ -13,7 +13,7 @@ using PresenceModel = Discord.API.Presence;
namespace Discord.WebSocket namespace Discord.WebSocket
{ {
/// <summary> /// <summary>
/// Represents a WebSocket guild user.
/// Represents a WebSocket-based guild user.
/// </summary> /// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketGuildUser : SocketUser, IGuildUser public class SocketGuildUser : SocketUser, IGuildUser


+ 3
- 2
src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs View File

@@ -1,13 +1,14 @@
using System.Diagnostics;
using System.Diagnostics;
using Model = Discord.API.Presence; using Model = Discord.API.Presence;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
//TODO: C#7 Candidate for record type
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public struct SocketPresence : IPresence public struct SocketPresence : IPresence
{ {
/// <inheritdoc />
public UserStatus Status { get; } public UserStatus Status { get; }
/// <inheritdoc />
public IActivity Activity { get; } public IActivity Activity { get; }


internal SocketPresence(UserStatus status, IActivity activity) internal SocketPresence(UserStatus status, IActivity activity)


+ 12
- 2
src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs View File

@@ -1,4 +1,4 @@
using Discord.Rest;
using Discord.Rest;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -9,17 +9,26 @@ namespace Discord.WebSocket
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketSelfUser : SocketUser, ISelfUser public class SocketSelfUser : SocketUser, ISelfUser
{ {
/// <inheritdoc />
public string Email { get; private set; } public string Email { get; private set; }
/// <inheritdoc />
public bool IsVerified { get; private set; } public bool IsVerified { get; private set; }
/// <inheritdoc />
public bool IsMfaEnabled { get; private set; } public bool IsMfaEnabled { get; private set; }
internal override SocketGlobalUser GlobalUser { get; } internal override SocketGlobalUser GlobalUser { get; }


/// <inheritdoc />
public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } }
/// <inheritdoc />
public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } }
/// <inheritdoc />
public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } }
/// <inheritdoc />
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } }
/// <inheritdoc />
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } }


/// <inheritdoc />
public override bool IsWebhook => false; public override bool IsWebhook => false;


internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser)
@@ -53,7 +62,8 @@ namespace Discord.WebSocket
} }
return hasGlobalChanges; return hasGlobalChanges;
} }

/// <inheritdoc />
public Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null) public Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null)
=> UserHelper.ModifyAsync(this, Discord, func, options); => UserHelper.ModifyAsync(this, Discord, func, options);




+ 3
- 2
src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics; using System.Diagnostics;
using Model = Discord.API.User; using Model = Discord.API.User;


@@ -15,7 +15,8 @@ namespace Discord.WebSocket
public override bool IsWebhook => false; public override bool IsWebhook => false;


internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } }
internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } }
internal override SocketGlobalUser GlobalUser =>
throw new NotSupportedException();


internal SocketUnknownUser(DiscordSocketClient discord, ulong id) internal SocketUnknownUser(DiscordSocketClient discord, ulong id)
: base(discord, id) : base(discord, id)


+ 12
- 2
src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs View File

@@ -1,11 +1,15 @@
using Discord.Rest; using Discord.Rest;
using System; using System;
using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.User; using Model = Discord.API.User;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
/// <summary> The WebSocket variant of <see cref="IUser"/>. Represents a Discord user. </summary>
/// <summary>
/// Represents a WebSocket-based user.
/// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public abstract class SocketUser : SocketEntity<ulong>, IUser public abstract class SocketUser : SocketEntity<ulong>, IUser
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -68,7 +72,7 @@ namespace Discord.WebSocket


/// <inheritdoc /> /// <inheritdoc />
public async Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null) public async Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null)
=> GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options) as IDMChannel;
=> GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false) as IDMChannel;


/// <inheritdoc /> /// <inheritdoc />
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
@@ -78,6 +82,12 @@ namespace Discord.WebSocket
public string GetDefaultAvatarUrl() public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);


/// <summary>
/// Gets the full name of the user (e.g. Example#0001).
/// </summary>
/// <returns>
/// The full name of the user.
/// </returns>
public override string ToString() => $"{Username}#{Discriminator}"; public override string ToString() => $"{Username}#{Discriminator}";
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})";
internal SocketUser Clone() => MemberwiseClone() as SocketUser; internal SocketUser Clone() => MemberwiseClone() as SocketUser;


+ 17
- 3
src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs View File

@@ -1,10 +1,9 @@
using System;
using System;
using System.Diagnostics; using System.Diagnostics;
using Model = Discord.API.VoiceState; using Model = Discord.API.VoiceState;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
//TODO: C#7 Candidate for record type
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public struct SocketVoiceState : IVoiceState public struct SocketVoiceState : IVoiceState
{ {
@@ -22,14 +21,23 @@ namespace Discord.WebSocket
} }


private readonly Flags _voiceStates; private readonly Flags _voiceStates;

/// <summary>
/// Gets the voice channel that the user is currently in; or <see langword="null"/> if none.
/// </summary>
public SocketVoiceChannel VoiceChannel { get; } public SocketVoiceChannel VoiceChannel { get; }
/// <inheritdoc />
public string VoiceSessionId { get; } public string VoiceSessionId { get; }


/// <inheritdoc />
public bool IsMuted => (_voiceStates & Flags.Muted) != 0; public bool IsMuted => (_voiceStates & Flags.Muted) != 0;
/// <inheritdoc />
public bool IsDeafened => (_voiceStates & Flags.Deafened) != 0; public bool IsDeafened => (_voiceStates & Flags.Deafened) != 0;
/// <inheritdoc />
public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0; public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0;
/// <inheritdoc />
public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0;
/// <inheritdoc />
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0;


internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed) internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed)
@@ -55,6 +63,12 @@ namespace Discord.WebSocket
return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress); return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress);
} }


/// <summary>
/// Gets the name of the voice channel.
/// </summary>
/// <returns>
/// The name of the voice channel.
/// </returns>
public override string ToString() => VoiceChannel?.Name ?? "Unknown"; public override string ToString() => VoiceChannel?.Name ?? "Unknown";
private string DebuggerDisplay => $"{VoiceChannel?.Name ?? "Unknown"} ({_voiceStates})"; private string DebuggerDisplay => $"{VoiceChannel?.Name ?? "Unknown"} ({_voiceStates})";
internal SocketVoiceState Clone() => this; internal SocketVoiceState Clone() => this;


+ 6
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs View File

@@ -63,26 +63,32 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue);
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Webhook users cannot be kicked.</exception>
Task IGuildUser.KickAsync(string reason, RequestOptions options) => Task IGuildUser.KickAsync(string reason, RequestOptions options) =>
throw new NotSupportedException("Webhook users cannot be kicked."); throw new NotSupportedException("Webhook users cannot be kicked.");


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Webhook users cannot be modified.</exception>
Task IGuildUser.ModifyAsync(Action<GuildUserProperties> func, RequestOptions options) => Task IGuildUser.ModifyAsync(Action<GuildUserProperties> func, RequestOptions options) =>
throw new NotSupportedException("Webhook users cannot be modified."); throw new NotSupportedException("Webhook users cannot be modified.");


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception>
Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) =>
throw new NotSupportedException("Roles are not supported on webhook users."); throw new NotSupportedException("Roles are not supported on webhook users.");


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception>
Task IGuildUser.AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options) => Task IGuildUser.AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options) =>
throw new NotSupportedException("Roles are not supported on webhook users."); throw new NotSupportedException("Roles are not supported on webhook users.");


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception>
Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) =>
throw new NotSupportedException("Roles are not supported on webhook users."); throw new NotSupportedException("Roles are not supported on webhook users.");


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception>
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) => Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) =>
throw new NotSupportedException("Roles are not supported on webhook users."); throw new NotSupportedException("Roles are not supported on webhook users.");




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

@@ -1,4 +1,4 @@
#if DEFAULTWEBSOCKET
#if DEFAULTWEBSOCKET
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
@@ -248,4 +248,4 @@ namespace Discord.Net.WebSockets
} }
} }
} }
#endif
#endif

+ 2
- 1
src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs View File

@@ -8,6 +8,7 @@ namespace Discord.Net.WebSockets
#if DEFAULTWEBSOCKET #if DEFAULTWEBSOCKET
public static readonly WebSocketProvider Instance = Create(); public static readonly WebSocketProvider Instance = Create();


/// <exception cref="PlatformNotSupportedException">The default WebSocketProvider is not supported on this platform.</exception>
public static WebSocketProvider Create(IWebProxy proxy = null) public static WebSocketProvider Create(IWebProxy proxy = null)
{ {
return () => return () =>
@@ -30,4 +31,4 @@ namespace Discord.Net.WebSockets
}; };
#endif #endif
} }
}
}

+ 3
- 2
src/Discord.Net.Webhook/WebhookClientHelper.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -12,11 +12,12 @@ namespace Discord.Webhook
{ {
internal static class WebhookClientHelper internal static class WebhookClientHelper
{ {
/// <exception cref="InvalidOperationException">Could not find a webhook with the supplied credentials.</exception>
public static async Task<RestInternalWebhook> GetWebhookAsync(DiscordWebhookClient client, ulong webhookId) public static async Task<RestInternalWebhook> GetWebhookAsync(DiscordWebhookClient client, ulong webhookId)
{ {
var model = await client.ApiClient.GetWebhookAsync(webhookId).ConfigureAwait(false); var model = await client.ApiClient.GetWebhookAsync(webhookId).ConfigureAwait(false);
if (model == null) if (model == null)
throw new InvalidOperationException("Could not find a webhook for the supplied credentials.");
throw new InvalidOperationException("Could not find a webhook with the supplied credentials.");
return RestInternalWebhook.Create(client, model); return RestInternalWebhook.Create(client, model);
} }
public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client, public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client,


Loading…
Cancel
Save