Browse Source

Merge branch 'discord-net:dev' into clearusercache

pull/1767/head
Daniel Baynton 4 years ago
parent
commit
0c2a05a4ef
100 changed files with 908 additions and 771 deletions
  1. +31
    -0
      CHANGELOG.md
  2. +1
    -1
      Discord.Net.targets
  3. +0
    -2
      README.md
  4. +1
    -1
      docs/_overwrites/Common/EmbedBuilder.Overwrites.md
  5. +1
    -1
      docs/guides/commands/intro.md
  6. +1
    -1
      samples/03_sharded_client/Modules/PublicModule.cs
  7. +1
    -1
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  8. +2
    -2
      src/Discord.Net.Commands/CommandService.cs
  9. +2
    -14
      src/Discord.Net.Core/DiscordConfig.cs
  10. +15
    -0
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  11. +0
    -21
      src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs
  12. +9
    -51
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  13. +2
    -2
      src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs
  14. +0
    -8
      src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs
  15. +6
    -50
      src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
  16. +8
    -0
      src/Discord.Net.Core/Entities/Messages/IMessage.cs
  17. +67
    -0
      src/Discord.Net.Core/Entities/Messages/ISticker.cs
  18. +0
    -12
      src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
  19. +0
    -3
      src/Discord.Net.Core/Entities/Messages/MessageType.cs
  20. +15
    -0
      src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs
  21. +0
    -5
      src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs
  22. +0
    -3
      src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
  23. +0
    -2
      src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
  24. +3
    -3
      src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
  25. +7
    -3
      src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs
  26. +36
    -1
      src/Discord.Net.Core/Entities/Users/IGuildUser.cs
  27. +0
    -4
      src/Discord.Net.Core/Entities/Users/IPresence.cs
  28. +3
    -3
      src/Discord.Net.Core/Entities/Users/IUser.cs
  29. +4
    -6
      src/Discord.Net.Core/Entities/Users/UserProperties.cs
  30. +0
    -10
      src/Discord.Net.Core/Extensions/StringExtensions.cs
  31. +3
    -3
      src/Discord.Net.Core/Extensions/UserExtensions.cs
  32. +11
    -0
      src/Discord.Net.Core/GatewayIntents.cs
  33. +0
    -18
      src/Discord.Net.Core/RateLimitPrecision.cs
  34. +0
    -2
      src/Discord.Net.Core/TokenType.cs
  35. +1
    -1
      src/Discord.Net.Core/Utils/SnowflakeUtils.cs
  36. +2
    -2
      src/Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs
  37. +1
    -1
      src/Discord.Net.Rest/API/Common/AuditLogEntry.cs
  38. +0
    -4
      src/Discord.Net.Rest/API/Common/Guild.cs
  39. +0
    -13
      src/Discord.Net.Rest/API/Common/GuildEmbed.cs
  40. +2
    -0
      src/Discord.Net.Rest/API/Common/InviteVanity.cs
  41. +2
    -0
      src/Discord.Net.Rest/API/Common/Message.cs
  42. +2
    -2
      src/Discord.Net.Rest/API/Common/Overwrite.cs
  43. +0
    -2
      src/Discord.Net.Rest/API/Common/Presence.cs
  44. +1
    -1
      src/Discord.Net.Rest/API/Common/Role.cs
  45. +25
    -0
      src/Discord.Net.Rest/API/Common/Sticker.cs
  46. +2
    -2
      src/Discord.Net.Rest/API/Common/UserGuild.cs
  47. +5
    -5
      src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs
  48. +2
    -2
      src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs
  49. +16
    -0
      src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs
  50. +0
    -11
      src/Discord.Net.Rest/API/Rest/SuppressEmbedParams.cs
  51. +10
    -14
      src/Discord.Net.Rest/ClientHelper.cs
  52. +48
    -42
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  53. +14
    -8
      src/Discord.Net.Rest/DiscordRestClient.cs
  54. +9
    -1
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs
  55. +5
    -2
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs
  56. +10
    -4
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs
  57. +10
    -4
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs
  58. +1
    -1
      src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs
  59. +17
    -10
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  60. +4
    -0
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  61. +4
    -0
      src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
  62. +4
    -0
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  63. +13
    -28
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  64. +3
    -51
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  65. +0
    -25
      src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs
  66. +0
    -3
      src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs
  67. +59
    -6
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  68. +4
    -0
      src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
  69. +17
    -3
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  70. +48
    -0
      src/Discord.Net.Rest/Entities/Messages/Sticker.cs
  71. +2
    -2
      src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs
  72. +16
    -4
      src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
  73. +4
    -4
      src/Discord.Net.Rest/Entities/Users/RestUser.cs
  74. +18
    -10
      src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
  75. +6
    -6
      src/Discord.Net.Rest/Entities/Users/UserHelper.cs
  76. +4
    -2
      src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
  77. +0
    -2
      src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs
  78. +0
    -44
      src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs
  79. +0
    -2
      src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
  80. +3
    -1
      src/Discord.Net.WebSocket/API/Gateway/Reaction.cs
  81. +18
    -23
      src/Discord.Net.WebSocket/BaseSocketClient.cs
  82. +9
    -14
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  83. +3
    -7
      src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
  84. +80
    -70
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  85. +3
    -8
      src/Discord.Net.WebSocket/DiscordSocketConfig.cs
  86. +33
    -24
      src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs
  87. +4
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs
  88. +4
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
  89. +3
    -35
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  90. +0
    -3
      src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs
  91. +4
    -0
      src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
  92. +17
    -3
      src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
  93. +0
    -2
      src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
  94. +17
    -5
      src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
  95. +4
    -6
      src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs
  96. +1
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
  97. +2
    -4
      src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
  98. +28
    -8
      src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
  99. +29
    -0
      src/Discord.Net.Webhook/DiscordWebhookClient.cs
  100. +26
    -0
      src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs

+ 31
- 0
CHANGELOG.md View File

@@ -1,5 +1,36 @@
# Changelog

## [2.4.0] - 2021-05-22
### Added
- #1726 Add stickers (91a9063)
- #1753 Webhook message edit & delete functionality (f67cd8e)
- #1757 Add ability to add/remove roles by id (4c9910c)
- #1781 Add GetEmotesAsync to IGuild (df23d57)
- #1801 Add missing property to MESSAGE_REACTION_ADD event (0715d7d)
- #1828 Add methods to interact with reactions without a message object (5b244f2)
- #1830 Add ModifyMessageAsync to IMessageChannel (365a848)
- #1844 Add Discord Certified Moderator user flag (4b8d444)

### Fixed
- #1486 Add type reader when entity type reader exists (c46daaa)
- #1835 Cached message emoji cleanup at MESSAGE_REACTION_REMOVE_EMOJI (8afef82)

### Misc
- #1778 Remove URI check from EmbedBuilder (25b04c4)
- #1800 Fix spelling in SnowflakeUtils.FromSnowflake (6aff419)

## [2.3.1] - 2021-03-10
### Fixed
- #1761 Deadlock in DiscordShardedClient when Ready is never received (73e5cc2)
- #1773 Private methods aren't added as commands (0fc713a)
- #1780 NullReferenceException in pin/unpin audit logs (f794163)
- #1786 Add ChannelType property to ChannelInfo audit log (6ac5ea1)
- #1791 Update Webhook ChannelId from model change (d2518db)
- #1794 Audit log UserId can be null (d41aeee)

### Misc
- #1774 Add remark regarding CustomStatus as the activity (51b7afe)

## [2.3.0] - 2021-01-28
### Added
- #1491 Add INVITE_CREATE and INVITE_DELETE events (1ab670b)


+ 1
- 1
Discord.Net.targets View File

@@ -1,6 +1,6 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VersionPrefix>2.3.1</VersionPrefix>
<VersionPrefix>3.0.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<LangVersion>latest</LangVersion>
<Authors>Discord.Net Contributors</Authors>


+ 0
- 2
README.md View File

@@ -8,8 +8,6 @@ An unofficial .NET API Wrapper for the Discord client (https://discord.com).

## Documentation

- [Stable](https://discord.foxbot.me/)
- Hosted by @foxbot
- [Nightly](https://docs.stillu.cc/)
- [Latest CI repo](https://github.com/discord-net/docs-static)



+ 1
- 1
docs/_overwrites/Common/EmbedBuilder.Overwrites.md View File

@@ -28,7 +28,7 @@ public async Task SendRichEmbedAsync()
var embed = new EmbedBuilder
{
// Embed property can be set within object initializer
Title = "Hello world!"
Title = "Hello world!",
Description = "I am a description set by initializer."
};
// Or with methods


+ 1
- 1
docs/guides/commands/intro.md View File

@@ -134,7 +134,7 @@ If, for whatever reason, you have two commands which are ambiguous to
each other, you may use the @Discord.Commands.PriorityAttribute to
specify which should be tested before the other.

The `Priority` attributes are sorted in ascending order; the higher
The `Priority` attributes are sorted in descending order; the higher
priority will be called first.

### Command Context


+ 1
- 1
samples/03_sharded_client/Modules/PublicModule.cs View File

@@ -9,7 +9,7 @@ namespace _03_sharded_client.Modules
[Command("info")]
public async Task InfoAsync()
{
var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards} shards!
var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards!
This guild is being served by shard number {Context.Client.GetShardFor(Context.Guild).ShardId}";
await ReplyAsync(msg);
}


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

@@ -136,7 +136,7 @@ namespace Discord.Commands
builder.Name = typeInfo.Name;

// Get all methods (including from inherited members), that are valid commands
var validCommands = typeInfo.GetMethods().Where(IsValidCommandDefinition);
var validCommands = typeInfo.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(IsValidCommandDefinition);

foreach (var method in validCommands)
{


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

@@ -408,7 +408,7 @@ namespace Discord.Commands
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsEnum)
return true;
return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.TypeReaderType));
return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.EntityType));
}
internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader)
{
@@ -511,7 +511,7 @@ namespace Discord.Commands
await _commandExecutedEvent.InvokeAsync(Optional.Create<CommandInfo>(), context, searchResult).ConfigureAwait(false);
return searchResult;
}

var commands = searchResult.Commands;
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();


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

@@ -16,7 +16,7 @@ namespace Discord
/// <see href="https://discord.com/developers/docs/reference#api-versioning">Discord API documentation</see>
/// .</para>
/// </returns>
public const int APIVersion = 6;
public const int APIVersion = 9;
/// <summary>
/// Returns the Voice API version Discord.Net uses.
/// </summary>
@@ -43,7 +43,7 @@ namespace Discord
/// <returns>
/// The user agent used in each Discord.Net request.
/// </returns>
public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})";
public static string UserAgent { get; } = $"DiscordBot (https://github.com/discord-net/Discord.Net, v{Version})";
/// <summary>
/// Returns the base Discord API URL.
/// </summary>
@@ -141,18 +141,6 @@ namespace Discord
/// </remarks>
internal bool DisplayInitialLog { get; set; } = true;

/// <summary>
/// Gets or sets the level of precision of the rate limit reset response.
/// </summary>
/// <remarks>
/// If set to <see cref="RateLimitPrecision.Second"/>, this value will be rounded up to the
/// nearest second.
/// </remarks>
/// <returns>
/// The currently set <see cref="RateLimitPrecision"/>.
/// </returns>
public RateLimitPrecision RateLimitPrecision { get; set; } = RateLimitPrecision.Millisecond;

/// <summary>
/// Gets or sets whether or not rate-limits should use the system clock.
/// </summary>


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

@@ -257,6 +257,21 @@ namespace Discord
/// </returns>
Task DeleteMessageAsync(IMessage message, RequestOptions options = null);

/// <summary>
/// Modifies a message.
/// </summary>
/// <remarks>
/// This method modifies this message with the specified properties. To see an example of this
/// method and what properties are available, please refer to <see cref="MessageProperties"/>.
/// </remarks>
/// <param name="messageId">The snowflake identifier of the message that would be changed.</param>
/// <param name="func">A delegate containing the properties to modify the message with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null);

/// <summary>
/// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds.
/// </summary>


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

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

+ 9
- 51
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -28,13 +28,6 @@ namespace Discord
/// </returns>
int AFKTimeout { get; }
/// <summary>
/// Gets a value that indicates whether this guild is embeddable (i.e. can use widget).
/// </summary>
/// <returns>
/// <see langword="true" /> if this guild has a widget enabled; otherwise <see langword="false" />.
/// </returns>
bool IsEmbeddable { get; }
/// <summary>
/// Gets a value that indicates whether this guild has the widget enabled.
/// </summary>
/// <returns>
@@ -132,29 +125,6 @@ namespace Discord
/// </returns>
ulong? AFKChannelId { get; }
/// <summary>
/// Gets the ID of the default channel for this guild.
/// </summary>
/// <remarks>
/// This property retrieves the snowflake identifier of the first viewable text channel for this guild.
/// <note type="warning">
/// This channel does not guarantee the user can send message to it, as it only looks for the first viewable
/// text channel.
/// </note>
/// </remarks>
/// <returns>
/// A <see langword="ulong"/> representing the snowflake identifier of the default text channel; <c>0</c> if
/// none can be found.
/// </returns>
ulong DefaultChannelId { get; }
/// <summary>
/// Gets the ID of the widget embed channel of this guild.
/// </summary>
/// <returns>
/// A <see langword="ulong"/> representing the snowflake identifier of the embedded channel found within the
/// widget settings of this guild; <see langword="null" /> if none is set.
/// </returns>
ulong? EmbedChannelId { get; }
/// <summary>
/// Gets the ID of the channel assigned to the widget of this guild.
/// </summary>
/// <returns>
@@ -364,16 +334,6 @@ namespace Discord
/// </returns>
Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null);
/// <summary>
/// Modifies this guild's embed channel.
/// </summary>
/// <param name="func">The delegate containing the properties to modify the guild widget with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")]
Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null);
/// <summary>
/// Modifies this guild's widget.
/// </summary>
/// <param name="func">The delegate containing the properties to modify the guild widget with.</param>
@@ -592,17 +552,6 @@ namespace Discord
/// </returns>
Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the embed channel set
/// within the server's widget settings; <see langword="null" /> if none is set.
/// </returns>
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")]
Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
@@ -892,6 +841,15 @@ namespace Discord
/// </returns>
Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null);

/// <summary>
/// Gets a collection of emotes from this guild.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of emotes found within the guild.
/// </returns>
Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null);
/// <summary>
/// Gets a specific emote from this guild.
/// </summary>


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

@@ -8,10 +8,10 @@ namespace Discord
/// <summary>
/// The target of the permission is a role.
/// </summary>
Role,
Role = 0,
/// <summary>
/// The target of the permission is a user.
/// </summary>
User
User = 1,
}
}

+ 0
- 8
src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs View File

@@ -16,14 +16,6 @@ namespace Discord
/// </returns>
bool IsTemporary { get; }
/// <summary>
/// Gets a value that indicates whether the invite has been revoked.
/// </summary>
/// <returns>
/// <c>true</c> if this invite was revoked; otherwise <c>false</c>.
/// </returns>
[Obsolete("This property doesn't exist anymore and shouldn't be used.")]
bool IsRevoked { get; }
/// <summary>
/// Gets the time (in seconds) until the invite expires.
/// </summary>
/// <returns>


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

@@ -12,7 +12,6 @@ namespace Discord
{
private string _title;
private string _description;
private string _url;
private EmbedImage? _image;
private EmbedThumbnail? _thumbnail;
private List<EmbedFieldBuilder> _fields;
@@ -70,26 +69,14 @@ namespace Discord
/// <summary> Gets or sets the URL of an <see cref="Embed"/>. </summary>
/// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception>
/// <returns> The URL of the embed.</returns>
public string Url
{
get => _url;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(Url));
_url = value;
}
}
public string Url { get; set; }
/// <summary> Gets or sets the thumbnail URL of an <see cref="Embed"/>. </summary>
/// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception>
/// <returns> The thumbnail URL of the embed.</returns>
public string ThumbnailUrl
{
get => _thumbnail?.Url;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(ThumbnailUrl));
_thumbnail = new EmbedThumbnail(value, null, null, null);
}
set => _thumbnail = new EmbedThumbnail(value, null, null, null);
}
/// <summary> Gets or sets the image URL of an <see cref="Embed"/>. </summary>
/// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception>
@@ -97,11 +84,7 @@ namespace Discord
public string ImageUrl
{
get => _image?.Url;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(ImageUrl));
_image = new EmbedImage(value, null, null, null);
}
set => _image = new EmbedImage(value, null, null, null);
}

/// <summary> Gets or sets the list of <see cref="EmbedFieldBuilder"/> of an <see cref="Embed"/>. </summary>
@@ -553,8 +536,6 @@ namespace Discord
public class EmbedAuthorBuilder
{
private string _name;
private string _url;
private string _iconUrl;
/// <summary>
/// Gets the maximum author name length allowed by Discord.
/// </summary>
@@ -585,15 +566,7 @@ namespace Discord
/// <returns>
/// The URL of the author field.
/// </returns>
public string Url
{
get => _url;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(Url));
_url = value;
}
}
public string Url { get; set; }
/// <summary>
/// Gets or sets the icon URL of the author field.
/// </summary>
@@ -601,15 +574,7 @@ namespace Discord
/// <returns>
/// The icon URL of the author field.
/// </returns>
public string IconUrl
{
get => _iconUrl;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(IconUrl));
_iconUrl = value;
}
}
public string IconUrl { get; set; }

/// <summary>
/// Sets the name of the author field.
@@ -671,7 +636,6 @@ namespace Discord
public class EmbedFooterBuilder
{
private string _text;
private string _iconUrl;

/// <summary>
/// Gets the maximum footer length allowed by Discord.
@@ -703,15 +667,7 @@ namespace Discord
/// <returns>
/// The icon URL of the footer field.
/// </returns>
public string IconUrl
{
get => _iconUrl;
set
{
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(IconUrl));
_iconUrl = value;
}
}
public string IconUrl { get; set; }

/// <summary>
/// Sets the name of the footer field.


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

@@ -164,6 +164,14 @@ namespace Discord
/// </summary>
IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions { get; }

/// <summary>
/// Gets all stickers included in this message.
/// </summary>
/// <returns>
/// A read-only collection of sticker objects.
/// </returns>
IReadOnlyCollection<ISticker> Stickers { get; }
/// <summary>
/// Gets the flags related to this message.
/// </summary>


+ 67
- 0
src/Discord.Net.Core/Entities/Messages/ISticker.cs View File

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

namespace Discord
{
/// <summary>
/// Represents a discord sticker.
/// </summary>
public interface ISticker
{
/// <summary>
/// Gets the ID of this sticker.
/// </summary>
/// <returns>
/// A snowflake ID associated with this sticker.
/// </returns>
ulong Id { get; }
/// <summary>
/// Gets the ID of the pack of this sticker.
/// </summary>
/// <returns>
/// A snowflake ID associated with the pack of this sticker.
/// </returns>
ulong PackId { get; }
/// <summary>
/// Gets the name of this sticker.
/// </summary>
/// <returns>
/// A <see langword="string"/> with the name of this sticker.
/// </returns>
string Name { get; }
/// <summary>
/// Gets the description of this sticker.
/// </summary>
/// <returns>
/// A <see langword="string"/> with the description of this sticker.
/// </returns>
string Description { get; }
/// <summary>
/// Gets the list of tags of this sticker.
/// </summary>
/// <returns>
/// A read-only list with the tags of this sticker.
/// </returns>
IReadOnlyCollection<string> Tags { get; }
/// <summary>
/// Gets the asset hash of this sticker.
/// </summary>
/// <returns>
/// A <see langword="string"/> with the asset hash of this sticker.
/// </returns>
string Asset { get; }
/// <summary>
/// Gets the preview asset hash of this sticker.
/// </summary>
/// <returns>
/// A <see langword="string"/> with the preview asset hash of this sticker.
/// </returns>
string PreviewAsset { get; }
/// <summary>
/// Gets the format type of this sticker.
/// </summary>
/// <returns>
/// A <see cref="StickerFormatType"/> with the format type of this sticker.
/// </returns>
StickerFormatType FormatType { get; }
}
}

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

@@ -36,18 +36,6 @@ namespace Discord
/// </returns>
Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null);
/// <summary>
/// Modifies the suppression of this message.
/// </summary>
/// <remarks>
/// This method modifies whether or not embeds in this message are suppressed (hidden).
/// </remarks>
/// <param name="suppressEmbeds">Whether or not embeds in this message should be suppressed.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null);
/// <summary>
/// Adds this message to its channel's pinned messages.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>


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

@@ -60,9 +60,6 @@ namespace Discord
/// <summary>
/// The message is an inline reply.
/// </summary>
/// <remarks>
/// Only available in API v8.
/// </remarks>
Reply = 19,
}
}

+ 15
- 0
src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs View File

@@ -0,0 +1,15 @@
namespace Discord
{
/// <summary> Defines the types of formats for stickers. </summary>
public enum StickerFormatType
{
/// <summary> Default value for a sticker format type. </summary>
None = 0,
/// <summary> The sticker format type is png. </summary>
Png = 1,
/// <summary> The sticker format type is apng. </summary>
Apng = 2,
/// <summary> The sticker format type is lottie. </summary>
Lottie = 3,
}
}

+ 0
- 5
src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs View File

@@ -22,11 +22,6 @@ namespace Discord
/// </summary>
AddReactions = 0x00_00_00_40,
/// <summary>
/// Allows for reading of messages. This flag is obsolete, use <see cref = "ViewChannel" /> instead.
/// </summary>
[Obsolete("Use ViewChannel instead.")]
ReadMessages = ViewChannel,
/// <summary>
/// Allows guild members to view a channel, which includes reading messages in text channels.
/// </summary>
ViewChannel = 0x00_00_04_00,


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

@@ -45,9 +45,6 @@ namespace Discord

/// <summary> If <c>true</c>, a user may add reactions. </summary>
public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions);
/// <summary> If <c>true</c>, a user may join channels. </summary>
[Obsolete("Use ViewChannel instead.")]
public bool ReadMessages => ViewChannel;
/// <summary> If <c>true</c>, a user may view channels. </summary>
public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel);



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

@@ -65,8 +65,6 @@ namespace Discord
/// Allows for viewing of audit logs.
/// </summary>
ViewAuditLog = 0x00_00_00_80,
[Obsolete("Use ViewChannel instead.")]
ReadMessages = ViewChannel,
ViewChannel = 0x00_00_04_00,
SendMessages = 0x00_00_08_00,
/// <summary>


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

@@ -37,9 +37,6 @@ namespace Discord
/// <summary> If <c>true</c>, a user may view the guild insights. </summary>
public bool ViewGuildInsights => Permissions.GetValue(RawValue, GuildPermission.ViewGuildInsights);

/// <summary> If True, a user may join channels. </summary>
[Obsolete("Use ViewChannel instead.")]
public bool ReadMessages => ViewChannel;
/// <summary> If True, a user may view channels. </summary>
public bool ViewChannel => Permissions.GetValue(RawValue, GuildPermission.ViewChannel);
/// <summary> If True, a user may send messages. </summary>
@@ -90,6 +87,9 @@ namespace Discord
/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary>
public GuildPermissions(ulong rawValue) { RawValue = rawValue; }

/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value after converting to ulong. </summary>
public GuildPermissions(string rawValue) { RawValue = ulong.Parse(rawValue); }

private GuildPermissions(ulong initialValue,
bool? createInstantInvite = null,
bool? kickMembers = null,


+ 7
- 3
src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs View File

@@ -43,9 +43,6 @@ namespace Discord
/// <summary> If Allowed, a user may add reactions. </summary>
public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions);
/// <summary> If Allowed, a user may join channels. </summary>
[Obsolete("Use ViewChannel instead.")]
public PermValue ReadMessages => ViewChannel;
/// <summary> If Allowed, a user may join channels. </summary>
public PermValue ViewChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ViewChannel);
/// <summary> If Allowed, a user may send messages. </summary>
public PermValue SendMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessages);
@@ -93,6 +90,13 @@ namespace Discord
DenyValue = denyValue;
}

/// <summary> Creates a new OverwritePermissions with the provided allow and deny packed values after converting to ulong. </summary>
public OverwritePermissions(string allowValue, string denyValue)
{
AllowValue = ulong.Parse(allowValue);
DenyValue = ulong.Parse(denyValue);
}

private OverwritePermissions(ulong allowValue, ulong denyValue,
PermValue? createInstantInvite = null,
PermValue? manageChannel = null,


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

@@ -113,7 +113,15 @@ namespace Discord
/// A task that represents the asynchronous modification operation.
/// </returns>
Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null);

/// <summary>
/// Adds the specified role to this user in the guild.
/// </summary>
/// <param name="roleId">The role to be added to the user.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous role addition operation.
/// </returns>
Task AddRoleAsync(ulong roleId, RequestOptions options = null);
/// <summary>
/// Adds the specified role to this user in the guild.
/// </summary>
@@ -124,6 +132,15 @@ namespace Discord
/// </returns>
Task AddRoleAsync(IRole role, RequestOptions options = null);
/// <summary>
/// Adds the specified <paramref name="roleIds"/> to this user in the guild.
/// </summary>
/// <param name="roleIds">The roles to be added to the user.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous role addition operation.
/// </returns>
Task AddRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null);
/// <summary>
/// Adds the specified <paramref name="roles"/> to this user in the guild.
/// </summary>
/// <param name="roles">The roles to be added to the user.</param>
@@ -133,6 +150,15 @@ namespace Discord
/// </returns>
Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null);
/// <summary>
/// Removes the specified <paramref name="roleId"/> from this user in the guild.
/// </summary>
/// <param name="roleId">The role to be removed from the user.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous role removal operation.
/// </returns>
Task RemoveRoleAsync(ulong roleId, RequestOptions options = null);
/// <summary>
/// Removes the specified <paramref name="role"/> from this user in the guild.
/// </summary>
/// <param name="role">The role to be removed from the user.</param>
@@ -142,6 +168,15 @@ namespace Discord
/// </returns>
Task RemoveRoleAsync(IRole role, RequestOptions options = null);
/// <summary>
/// Removes the specified <paramref name="roleIds"/> from this user in the guild.
/// </summary>
/// <param name="roleIds">The roles to be removed from the user.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous role removal operation.
/// </returns>
Task RemoveRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null);
/// <summary>
/// Removes the specified <paramref name="roles"/> from this user in the guild.
/// </summary>
/// <param name="roles">The roles to be removed from the user.</param>


+ 0
- 4
src/Discord.Net.Core/Entities/Users/IPresence.cs View File

@@ -7,10 +7,6 @@ namespace Discord
/// </summary>
public interface IPresence
{
/// <summary>
/// Gets the activity this user is currently doing.
/// </summary>
IActivity Activity { get; }
/// <summary>
/// Gets the current status of this user.
/// </summary>


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

@@ -87,7 +87,7 @@ namespace Discord
UserProperties? PublicFlags { get; }

/// <summary>
/// Gets the direct message channel of this user, or create one if it does not already exist.
/// Creates the direct message channel of this user.
/// </summary>
/// <remarks>
/// This method is used to obtain or create a channel used to send a direct message.
@@ -102,7 +102,7 @@ namespace Discord
/// <example>
/// <para>The following example attempts to send a direct message to the target user and logs the incident should
/// it fail.</para>
/// <code region="GetOrCreateDMChannelAsync" language="cs"
/// <code region="CreateDMChannelAsync" language="cs"
/// source="../../../Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs"/>
/// </example>
/// <param name="options">The options to be used when sending the request.</param>
@@ -110,6 +110,6 @@ namespace Discord
/// A task that represents the asynchronous operation for getting or creating a DM channel. The task result
/// contains the DM channel associated with this user.
/// </returns>
Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null);
Task<IDMChannel> CreateDMChannelAsync(RequestOptions options = null);
}
}

+ 4
- 6
src/Discord.Net.Core/Entities/Users/UserProperties.cs View File

@@ -22,12 +22,6 @@ namespace Discord
/// </summary>
HypeSquadEvents = 1 << 2,
/// <summary>
/// Flag given to users who have participated in the bug report program.
/// This flag is obsolete, use <see cref="BugHunterLevel1"/> instead.
/// </summary>
[Obsolete("Use BugHunterLevel1 instead.")]
BugHunter = 1 << 3,
/// <summary>
/// Flag given to users who have participated in the bug report program and are level 1.
/// </summary>
BugHunterLevel1 = 1 << 3,
@@ -67,5 +61,9 @@ namespace Discord
/// Flag given to users that developed bots and early verified their accounts.
/// </summary>
EarlyVerifiedBotDeveloper = 1 << 17,
/// <summary>
/// Flag given to users that are discord certified moderators who has give discord's exam.
/// </summary>
DiscordCertifiedModerator = 1 << 18,
}
}

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

@@ -1,10 +0,0 @@
using System;

namespace Discord
{
internal static class StringExtensions
{
public static bool IsNullOrUri(this string url) =>
string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.Absolute);
}
}

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

@@ -42,7 +42,7 @@ namespace Discord
RequestOptions options = null,
AllowedMentions allowedMentions = null)
{
return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
}

/// <summary>
@@ -94,7 +94,7 @@ namespace Discord
RequestOptions options = null
)
{
return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false);
return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false);
}

/// <summary>
@@ -149,7 +149,7 @@ namespace Discord
Embed embed = null,
RequestOptions options = null)
{
return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false);
return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false);
}

/// <summary>


+ 11
- 0
src/Discord.Net.Core/GatewayIntents.cs View File

@@ -39,5 +39,16 @@ namespace Discord
DirectMessageReactions = 1 << 13,
/// <summary> This intent includes TYPING_START </summary>
DirectMessageTyping = 1 << 14,
/// <summary>
/// This intent includes all but <see cref="GuildMembers"/> and <see cref="GuildMembers"/>
/// that are privileged must be enabled for the application.
/// </summary>
AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites |
GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages |
DirectMessageReactions | DirectMessageTyping,
/// <summary>
/// This intent includes all of them, including privileged ones.
/// </summary>
All = AllUnprivileged | GuildMembers | GuildPresences
}
}

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

@@ -1,18 +0,0 @@
namespace Discord
{
/// <summary>
/// Specifies the level of precision to request in the rate limit
/// response header.
/// </summary>
public enum RateLimitPrecision
{
/// <summary>
/// Specifies precision rounded up to the nearest whole second
/// </summary>
Second,
/// <summary>
/// Specifies precision rounded to the nearest millisecond.
/// </summary>
Millisecond
}
}

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

@@ -5,8 +5,6 @@ namespace Discord
/// <summary> Specifies the type of token to use with the client. </summary>
public enum TokenType
{
[Obsolete("User logins are deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827", error: true)]
User,
/// <summary>
/// An OAuth2 token type.
/// </summary>


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

@@ -12,7 +12,7 @@ namespace Discord
/// </summary>
/// <param name="value">The snowflake identifier to resolve.</param>
/// <returns>
/// A <see cref="DateTimeOffset" /> representing the time for when the object is geenrated.
/// A <see cref="DateTimeOffset" /> representing the time for when the object is generated.
/// </returns>
public static DateTimeOffset FromSnowflake(ulong value)
=> DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL));


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

@@ -18,11 +18,11 @@ namespace Discord.Net.Examples.Core.Entities.Users

#endregion

#region GetOrCreateDMChannelAsync
#region CreateDMChannelAsync

public async Task MessageUserAsync(IUser user)
{
var channel = await user.GetOrCreateDMChannelAsync();
var channel = await user.CreateDMChannelAsync();
try
{
await channel.SendMessageAsync("Awesome stuff!");


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

@@ -7,7 +7,7 @@ namespace Discord.API
[JsonProperty("target_id")]
public ulong? TargetId { get; set; }
[JsonProperty("user_id")]
public ulong UserId { get; set; }
public ulong? UserId { get; set; }

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


+ 0
- 4
src/Discord.Net.Rest/API/Common/Guild.cs View File

@@ -23,10 +23,6 @@ namespace Discord.API
public ulong? AFKChannelId { get; set; }
[JsonProperty("afk_timeout")]
public int AFKTimeout { get; set; }
[JsonProperty("embed_enabled")]
public Optional<bool> EmbedEnabled { get; set; }
[JsonProperty("embed_channel_id")]
public Optional<ulong?> EmbedChannelId { get; set; }
[JsonProperty("verification_level")]
public VerificationLevel VerificationLevel { get; set; }
[JsonProperty("default_message_notifications")]


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

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

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

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

@@ -6,5 +6,7 @@ namespace Discord.API
{
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("uses")]
public int Uses { get; set; }
}
}

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

@@ -58,5 +58,7 @@ namespace Discord.API
public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("referenced_message")]
public Optional<Message> ReferencedMessage { get; set; }
[JsonProperty("stickers")]
public Optional<Sticker[]> Stickers { get; set; }
}
}

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

@@ -10,8 +10,8 @@ namespace Discord.API
[JsonProperty("type")]
public PermissionTarget TargetType { get; set; }
[JsonProperty("deny"), Int53]
public ulong Deny { get; set; }
public string Deny { get; set; }
[JsonProperty("allow"), Int53]
public ulong Allow { get; set; }
public string Allow { get; set; }
}
}

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

@@ -13,8 +13,6 @@ namespace Discord.API
public Optional<ulong> GuildId { get; set; }
[JsonProperty("status")]
public UserStatus Status { get; set; }
[JsonProperty("game")]
public Game Game { get; set; }

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


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

@@ -18,7 +18,7 @@ namespace Discord.API
[JsonProperty("position")]
public int Position { get; set; }
[JsonProperty("permissions"), Int53]
public ulong Permissions { get; set; }
public string Permissions { get; set; }
[JsonProperty("managed")]
public bool Managed { get; set; }
[JsonProperty("tags")]


+ 25
- 0
src/Discord.Net.Rest/API/Common/Sticker.cs View File

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

namespace Discord.API
{
internal class Sticker
{
[JsonProperty("id")]
public ulong Id { get; set; }
[JsonProperty("pack_id")]
public ulong PackId { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Desription { get; set; }
[JsonProperty("tags")]
public Optional<string> Tags { get; set; }
[JsonProperty("asset")]
public string Asset { get; set; }
[JsonProperty("preview_asset")]
public string PreviewAsset { get; set; }
[JsonProperty("format_type")]
public StickerFormatType FormatType { get; set; }
}
}

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

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

namespace Discord.API
@@ -14,6 +14,6 @@ namespace Discord.API
[JsonProperty("owner")]
public bool Owner { get; set; }
[JsonProperty("permissions"), Int53]
public ulong Permissions { get; set; }
public string Permissions { get; set; }
}
}

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

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

namespace Discord.API.Rest
@@ -7,13 +7,13 @@ namespace Discord.API.Rest
internal class ModifyChannelPermissionsParams
{
[JsonProperty("type")]
public string Type { get; }
public int Type { get; }
[JsonProperty("allow")]
public ulong Allow { get; }
public string Allow { get; }
[JsonProperty("deny")]
public ulong Deny { get; }
public string Deny { get; }

public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny)
public ModifyChannelPermissionsParams(int type, string allow, string deny)
{
Type = type;
Allow = allow;


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

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

namespace Discord.API.Rest
@@ -9,7 +9,7 @@ namespace Discord.API.Rest
[JsonProperty("name")]
public Optional<string> Name { get; set; }
[JsonProperty("permissions")]
public Optional<ulong> Permissions { get; set; }
public Optional<string> Permissions { get; set; }
[JsonProperty("color")]
public Optional<uint> Color { get; set; }
[JsonProperty("hoist")]


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

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

namespace Discord.API.Rest
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class ModifyWebhookMessageParams
{
[JsonProperty("content")]
public Optional<string> Content { get; set; }
[JsonProperty("embeds")]
public Optional<Embed[]> Embeds { get; set; }
[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }
}
}

+ 0
- 11
src/Discord.Net.Rest/API/Rest/SuppressEmbedParams.cs View File

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

namespace Discord.API.Rest
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class SuppressEmbedParams
{
[JsonProperty("suppress")]
public bool Suppressed { get; set; }
}
}

+ 10
- 14
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -17,7 +17,7 @@ namespace Discord.Rest
return RestApplication.Create(client, model);
}

public static async Task<RestChannel> GetChannelAsync(BaseDiscordClient client,
public static async Task<RestChannel> GetChannelAsync(BaseDiscordClient client,
ulong id, RequestOptions options)
{
var model = await client.ApiClient.GetChannelAsync(id, options).ConfigureAwait(false);
@@ -45,13 +45,13 @@ namespace Discord.Rest
.Where(x => x.Type == ChannelType.Group)
.Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray();
}
public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options)
{
var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false);
return models.Select(RestConnection.Create).ToImmutableArray();
}
public static async Task<RestInviteMetadata> GetInviteAsync(BaseDiscordClient client,
string inviteId, RequestOptions options)
{
@@ -60,7 +60,7 @@ namespace Discord.Rest
return RestInviteMetadata.Create(client, null, null, model);
return null;
}
public static async Task<RestGuild> GetGuildAsync(BaseDiscordClient client,
ulong id, bool withCounts, RequestOptions options)
{
@@ -69,14 +69,6 @@ namespace Discord.Rest
return RestGuild.Create(client, model);
return null;
}
public static async Task<RestGuildEmbed?> GetGuildEmbedAsync(BaseDiscordClient client,
ulong id, RequestOptions options)
{
var model = await client.ApiClient.GetGuildEmbedAsync(id, options).ConfigureAwait(false);
if (model != null)
return RestGuildEmbed.Create(model);
return null;
}
public static async Task<RestGuildWidget?> GetGuildWidgetAsync(BaseDiscordClient client,
ulong id, RequestOptions options)
{
@@ -85,7 +77,7 @@ namespace Discord.Rest
return RestGuildWidget.Create(model);
return null;
}
public static IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(BaseDiscordClient client,
public static IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(BaseDiscordClient client,
ulong? fromGuildId, int? limit, RequestOptions options)
{
return new PagedAsyncEnumerable<RestUserGuild>(
@@ -136,7 +128,7 @@ namespace Discord.Rest
var model = await client.ApiClient.CreateGuildAsync(args, options).ConfigureAwait(false);
return RestGuild.Create(client, model);
}
public static async Task<RestUser> GetUserAsync(BaseDiscordClient client,
ulong id, RequestOptions options)
{
@@ -201,5 +193,9 @@ namespace Discord.Rest
}
};
}
public static Task AddRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null)
=> client.ApiClient.AddRoleAsync(guildId, userId, roleId, options);
public static Task RemoveRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null)
=> client.ApiClient.RemoveRoleAsync(guildId, userId, roleId, options);
}
}

+ 48
- 42
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -45,20 +45,18 @@ namespace Discord.API
internal string AuthToken { get; private set; }
internal IRestClient RestClient { get; private set; }
internal ulong? CurrentUserId { get; set; }
public RateLimitPrecision RateLimitPrecision { get; private set; }
internal bool UseSystemClock { get; set; }

internal JsonSerializer Serializer => _serializer;

/// <exception cref="ArgumentException">Unknown OAuth token type.</exception>
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry,
JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true)
JsonSerializer serializer = null, bool useSystemClock = true)
{
_restClientProvider = restClientProvider;
UserAgent = userAgent;
DefaultRetryMode = defaultRetryMode;
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
RateLimitPrecision = rateLimitPrecision;
UseSystemClock = useSystemClock;

RequestQueue = new RequestQueue();
@@ -75,14 +73,12 @@ namespace Discord.API
RestClient.SetHeader("accept", "*/*");
RestClient.SetHeader("user-agent", UserAgent);
RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken));
RestClient.SetHeader("X-RateLimit-Precision", RateLimitPrecision.ToString().ToLower());
}
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception>
internal static string GetPrefixedToken(TokenType tokenType, string token)
{
return tokenType switch
{
default(TokenType) => token,
TokenType.Bot => $"Bot {token}",
TokenType.Bearer => $"Bearer {token}",
_ => throw new ArgumentException(message: "Unknown OAuth token type.", paramName: nameof(tokenType)),
@@ -523,6 +519,43 @@ namespace Discord.API
var ids = new BucketIds(webhookId: webhookId);
return await SendJsonAsync<Message>("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", 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 ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, ModifyWebhookMessageParams args, RequestOptions options = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");

Preconditions.NotNull(args, nameof(args));
Preconditions.NotEqual(webhookId, 0, nameof(webhookId));
Preconditions.NotEqual(messageId, 0, nameof(messageId));

if (args.Embeds.IsSpecified)
Preconditions.AtMost(args.Embeds.Value.Length, 10, nameof(args.Embeds), "A max of 10 Embeds are allowed.");
if (args.Content.IsSpecified && args.Content.Value.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(webhookId: webhookId);
await SendJsonAsync<Message>("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}

/// <exception cref="InvalidOperationException">This operation may only be called with a <see cref="TokenType.Webhook"/> token.</exception>
public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");

Preconditions.NotEqual(webhookId, 0, nameof(webhookId));
Preconditions.NotEqual(messageId, 0, nameof(messageId));

options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(webhookId: webhookId);
await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", 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> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null)
{
@@ -608,16 +641,6 @@ namespace Discord.API
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}

public async Task SuppressEmbedAsync(ulong channelId, ulong messageId, Rest.SuppressEmbedParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotEqual(messageId, 0, nameof(messageId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(channelId: channelId);
await SendJsonAsync("POST", () => $"channels/{channelId}/messages/{messageId}/suppress-embeds", args, ids, options: options).ConfigureAwait(false);
}

public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
@@ -899,7 +922,7 @@ namespace Discord.API

var ids = new BucketIds(guildId: guildId);
string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={Uri.EscapeDataString(args.Reason)}";
await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false);
await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete_message_days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false);
}
/// <exception cref="ArgumentException"><paramref name="guildId"/> and <paramref name="userId"/> must not be equal to zero.</exception>
public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null)
@@ -912,32 +935,6 @@ namespace Discord.API
await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false);
}

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

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

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

//Guild Widget
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception>
public async Task<GuildWidget> GetGuildWidgetAsync(ulong guildId, RequestOptions options = null)
@@ -1243,6 +1240,15 @@ namespace Discord.API
}

//Guild emoji
public async Task<IReadOnlyCollection<Emoji>> GetGuildEmotesAsync(ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);
return await SendAsync<IReadOnlyCollection<Emoji>>("GET", () => $"guilds/{guildId}/emojis", ids, options: options).ConfigureAwait(false);
}

public async Task<Emoji> GetGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));


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

@@ -29,10 +29,7 @@ namespace Discord.Rest
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }

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

internal override void Dispose(bool disposing)
{
@@ -80,9 +77,6 @@ namespace Discord.Rest
=> ClientHelper.GetGuildAsync(this, id, false, options);
public Task<RestGuild> GetGuildAsync(ulong id, bool withCounts, RequestOptions options = null)
=> ClientHelper.GetGuildAsync(this, id, withCounts, options);
[Obsolete("This endpoint is deprecated, use GetGuildWidgetAsync instead.")]
public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetGuildEmbedAsync(this, id, options);
public Task<RestGuildWidget?> GetGuildWidgetAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetGuildWidgetAsync(this, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null)
@@ -107,7 +101,19 @@ namespace Discord.Rest
=> ClientHelper.GetVoiceRegionAsync(this, id, options);
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetWebhookAsync(this, id, options);

public Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId)
=> ClientHelper.AddRoleAsync(this, guildId, userId, roleId);
public Task RemoveRoleAsync(ulong guildId, ulong userId, ulong roleId)
=> ClientHelper.RemoveRoleAsync(this, guildId, userId, roleId);

public Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null)
=> MessageHelper.AddReactionAsync(channelId, messageId, emote, this, options);
public Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, IEmote emote, RequestOptions options = null)
=> MessageHelper.RemoveReactionAsync(channelId, messageId, userId, emote, this, options);
public Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options);
public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options);
//IDiscordClient
/// <inheritdoc />
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)


+ 9
- 1
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs View File

@@ -5,13 +5,14 @@ namespace Discord.Rest
/// </summary>
public struct ChannelInfo
{
internal ChannelInfo(string name, string topic, int? rateLimit, bool? nsfw, int? bitrate)
internal ChannelInfo(string name, string topic, int? rateLimit, bool? nsfw, int? bitrate, ChannelType? type)
{
Name = name;
Topic = topic;
SlowModeInterval = rateLimit;
IsNsfw = nsfw;
Bitrate = bitrate;
ChannelType = type;
}

/// <summary>
@@ -53,5 +54,12 @@ namespace Discord.Rest
/// <c>null</c> if this is not mentioned in this entry.
/// </returns>
public int? Bitrate { get; }
/// <summary>
/// Gets the type of this channel.
/// </summary>
/// <returns>
/// The channel type of this channel; <c>null</c> if not applicable.
/// </returns>
public ChannelType? ChannelType { get; }
}
}

+ 5
- 2
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs View File

@@ -26,6 +26,7 @@ namespace Discord.Rest
var rateLimitPerUserModel = changes.FirstOrDefault(x => x.ChangedProperty == "rate_limit_per_user");
var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw");
var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate");
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type");

string oldName = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer),
newName = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer);
@@ -37,9 +38,11 @@ namespace Discord.Rest
newNsfw = nsfwModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer);
int? oldBitrate = bitrateModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer),
newBitrate = bitrateModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer);
ChannelType? oldType = typeModel?.OldValue?.ToObject<ChannelType>(discord.ApiClient.Serializer),
newType = typeModel?.NewValue?.ToObject<ChannelType>(discord.ApiClient.Serializer);

var before = new ChannelInfo(oldName, oldTopic, oldRateLimitPerUser, oldNsfw, oldBitrate);
var after = new ChannelInfo(newName, newTopic, newRateLimitPerUser, newNsfw, newBitrate);
var before = new ChannelInfo(oldName, oldTopic, oldRateLimitPerUser, oldNsfw, oldBitrate, oldType);
var after = new ChannelInfo(newName, newTopic, newRateLimitPerUser, newNsfw, newBitrate, newType);

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


+ 10
- 4
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs View File

@@ -19,8 +19,14 @@ namespace Discord.Rest

internal static MessagePinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo));
RestUser user = null;
if (entry.TargetId.HasValue)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
user = RestUser.Create(discord, userInfo);
}

return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user);
}

/// <summary>
@@ -38,10 +44,10 @@ namespace Discord.Rest
/// </returns>
public ulong ChannelId { get; }
/// <summary>
/// Gets the user of the message that was pinned.
/// Gets the user of the message that was pinned if available.
/// </summary>
/// <returns>
/// A user object representing the user that created the pinned message.
/// A user object representing the user that created the pinned message or <see langword="null"/>.
/// </returns>
public IUser Target { get; }
}


+ 10
- 4
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs View File

@@ -19,8 +19,14 @@ namespace Discord.Rest

internal static MessageUnpinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo));
RestUser user = null;
if (entry.TargetId.HasValue)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
user = RestUser.Create(discord, userInfo);
}

return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user);
}

/// <summary>
@@ -38,10 +44,10 @@ namespace Discord.Rest
/// </returns>
public ulong ChannelId { get; }
/// <summary>
/// Gets the user of the message that was unpinned.
/// Gets the user of the message that was unpinned if available.
/// </summary>
/// <returns>
/// A user object representing the user that created the unpinned message.
/// A user object representing the user that created the unpinned message or <see langword="null"/>.
/// </returns>
public IUser Target { get; }
}


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

@@ -22,7 +22,7 @@ namespace Discord.Rest

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


+ 17
- 10
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -33,8 +33,8 @@ namespace Discord.Rest
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
};
@@ -59,8 +59,8 @@ namespace Discord.Rest
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
};
@@ -84,8 +84,8 @@ namespace Discord.Rest
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
};
@@ -286,6 +286,13 @@ namespace Discord.Rest
return RestUserMessage.Create(client, channel, client.CurrentUser, model);
}

public static async Task<RestUserMessage> ModifyMessageAsync(IMessageChannel channel, ulong messageId, Action<MessageProperties> func,
BaseDiscordClient client, RequestOptions options)
{
var msgModel = await MessageHelper.ModifyAsync(channel.Id, messageId, client, func, options).ConfigureAwait(false);
return RestUserMessage.Create(client, channel, msgModel.Author.IsSpecified ? RestUser.Create(client, msgModel.Author.Value) : client.CurrentUser, msgModel);
}

public static Task DeleteMessageAsync(IMessageChannel channel, ulong messageId, BaseDiscordClient client,
RequestOptions options)
=> MessageHelper.DeleteAsync(channel.Id, messageId, client, options);
@@ -321,13 +328,13 @@ namespace Discord.Rest
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client,
IUser user, OverwritePermissions perms, RequestOptions options)
{
var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue);
var args = new ModifyChannelPermissionsParams((int)PermissionTarget.User, perms.AllowValue.ToString(), perms.DenyValue.ToString());
await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args, options).ConfigureAwait(false);
}
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client,
IRole role, OverwritePermissions perms, RequestOptions options)
{
var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue);
var args = new ModifyChannelPermissionsParams((int)PermissionTarget.Role, perms.AllowValue.ToString(), perms.DenyValue.ToString());
await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args, options).ConfigureAwait(false);
}
public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client,
@@ -443,8 +450,8 @@ namespace Discord.Rest
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
};
await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);


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

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

/// <inheritdoc />
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);

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


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

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

/// <inheritdoc />
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);

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


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

@@ -152,6 +152,10 @@ namespace Discord.Rest
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

/// <inheritdoc />
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);

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


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

@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using EmbedModel = Discord.API.GuildEmbed;
using WidgetModel = Discord.API.GuildWidget;
using Model = Discord.API.Guild;
using RoleModel = Discord.API.Role;
@@ -81,26 +80,6 @@ namespace Discord.Rest
return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false);
}
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, BaseDiscordClient client,
Action<GuildEmbedProperties> func, RequestOptions options)
{
if (func == null) throw new ArgumentNullException(nameof(func));

var args = new GuildEmbedProperties();
func(args);
var apiArgs = new API.Rest.ModifyGuildEmbedParams
{
Enabled = args.Enabled
};

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

return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, apiArgs, options).ConfigureAwait(false);
}
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
public static async Task<WidgetModel> ModifyWidgetAsync(IGuild guild, BaseDiscordClient client,
Action<GuildWidgetProperties> func, RequestOptions options)
{
@@ -205,8 +184,8 @@ namespace Discord.Rest
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
};
@@ -233,8 +212,8 @@ namespace Discord.Rest
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
};
@@ -258,8 +237,8 @@ namespace Discord.Rest
{
TargetId = overwrite.TargetId,
TargetType = overwrite.TargetType,
Allow = overwrite.Permissions.AllowValue,
Deny = overwrite.Permissions.DenyValue
Allow = overwrite.Permissions.AllowValue.ToString(),
Deny = overwrite.Permissions.DenyValue.ToString()
}).ToArray()
: Optional.Create<API.Overwrite[]>(),
};
@@ -304,6 +283,7 @@ namespace Discord.Rest
var vanityModel = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false);
if (vanityModel == null) throw new InvalidOperationException("This guild does not have a vanity URL.");
var inviteModel = await client.ApiClient.GetInviteAsync(vanityModel.Code, options).ConfigureAwait(false);
inviteModel.Uses = vanityModel.Uses;
return RestInviteMetadata.Create(client, guild, null, inviteModel);
}

@@ -320,7 +300,7 @@ namespace Discord.Rest
Hoist = isHoisted,
Mentionable = isMentionable,
Name = name,
Permissions = permissions?.RawValue ?? Optional.Create<ulong>()
Permissions = permissions?.RawValue.ToString() ?? Optional.Create<string>()
};

var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, createGuildRoleParams, options).ConfigureAwait(false);
@@ -496,6 +476,11 @@ namespace Discord.Rest
}

//Emotes
public static async Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(IGuild guild, BaseDiscordClient client, RequestOptions options)
{
var models = await client.ApiClient.GetGuildEmotesAsync(guild.Id, options).ConfigureAwait(false);
return models.Select(x => x.ToEntity()).ToImmutableArray();
}
public static async Task<GuildEmote> GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options)
{
var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options).ConfigureAwait(false);


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

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

@@ -27,8 +26,6 @@ namespace Discord.Rest
/// <inheritdoc />
public int AFKTimeout { get; private set; }
/// <inheritdoc />
public bool IsEmbeddable { get; private set; }
/// <inheritdoc />
public bool IsWidgetEnabled { get; private set; }
/// <inheritdoc />
public VerificationLevel VerificationLevel { get; private set; }
@@ -42,8 +39,6 @@ namespace Discord.Rest
/// <inheritdoc />
public ulong? AFKChannelId { get; private set; }
/// <inheritdoc />
public ulong? EmbedChannelId { get; private set; }
/// <inheritdoc />
public ulong? WidgetChannelId { get; private set; }
/// <inheritdoc />
public ulong? SystemChannelId { get; private set; }
@@ -95,8 +90,6 @@ namespace Discord.Rest
/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);

[Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")]
public ulong DefaultChannelId => Id;
/// <inheritdoc />
public string IconUrl => CDN.GetGuildIconUrl(Id, IconId);
/// <inheritdoc />
@@ -133,16 +126,12 @@ namespace Discord.Rest
internal void Update(Model model)
{
AFKChannelId = model.AFKChannelId;
if (model.EmbedChannelId.IsSpecified)
EmbedChannelId = model.EmbedChannelId.Value;
if (model.WidgetChannelId.IsSpecified)
WidgetChannelId = model.WidgetChannelId.Value;
SystemChannelId = model.SystemChannelId;
RulesChannelId = model.RulesChannelId;
PublicUpdatesChannelId = model.PublicUpdatesChannelId;
AFKTimeout = model.AFKTimeout;
if (model.EmbedEnabled.IsSpecified)
IsEmbeddable = model.EmbedEnabled.Value;
if (model.WidgetEnabled.IsSpecified)
IsWidgetEnabled = model.WidgetEnabled.Value;
IconId = model.Icon;
@@ -200,11 +189,6 @@ namespace Discord.Rest

Available = true;
}
internal void Update(EmbedModel model)
{
EmbedChannelId = model.ChannelId;
IsEmbeddable = model.Enabled;
}
internal void Update(WidgetModel model)
{
WidgetChannelId = model.ChannelId;
@@ -241,15 +225,6 @@ namespace Discord.Rest
Update(model);
}

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

/// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public async Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null)
@@ -463,23 +438,6 @@ namespace Discord.Rest
.FirstOrDefault();
}

/// <summary>
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the embed channel set
/// within the server's widget settings; <see langword="null"/> if none is set.
/// </returns>
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")]
public async Task<RestGuildChannel> GetEmbedChannelAsync(RequestOptions options = null)
{
var embedId = EmbedChannelId;
if (embedId.HasValue)
return await GuildHelper.GetChannelAsync(this, Discord, embedId.Value, options).ConfigureAwait(false);
return null;
}

/// <summary>
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary>
@@ -828,6 +786,9 @@ namespace Discord.Rest

//Emotes
/// <inheritdoc />
public Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null)
=> GuildHelper.GetEmotesAsync(this, Discord, options);
/// <inheritdoc />
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetEmoteAsync(this, Discord, id, options);
/// <inheritdoc />
@@ -934,15 +895,6 @@ namespace Discord.Rest
return null;
}
/// <inheritdoc />
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")]
async Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetEmbedChannelAsync(options).ConfigureAwait(false);
else
return null;
}
/// <inheritdoc />
async Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)


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

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

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

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

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

+ 0
- 3
src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs View File

@@ -8,9 +8,6 @@ namespace Discord.Rest
{
private long _createdAtTicks;

/// <inheritdoc />
[Obsolete("This property doesn't exist anymore and shouldn't be used.")]
public bool IsRevoked { get; private set; }
/// <inheritdoc />
public bool IsTemporary { get; private set; }
/// <inheritdoc />


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

@@ -71,6 +71,48 @@ namespace Discord.Rest
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false);
}

public static async Task<Model> ModifyAsync(ulong channelId, ulong msgId, BaseDiscordClient client, Action<MessageProperties> func,
RequestOptions options)
{
var args = new MessageProperties();
func(args);

if ((args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value)) && (args.Embed.IsSpecified && args.Embed.Value == null))
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));

if (args.AllowedMentions.IsSpecified)
{
AllowedMentions allowedMentions = args.AllowedMentions.Value;
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");

// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}

if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
}

var apiArgs = new API.Rest.ModifyMessageParams
{
Content = args.Content,
Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create<API.Embed>(),
Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(),
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(),
};
return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false);
}

public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
=> DeleteAsync(msg.Channel.Id, msg.Id, client, options);

@@ -80,13 +122,9 @@ namespace Discord.Rest
await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false);
}

public static async Task SuppressEmbedsAsync(IMessage msg, BaseDiscordClient client, bool suppress, RequestOptions options)
public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options)
{
var apiArgs = new API.Rest.SuppressEmbedParams
{
Suppressed = suppress
};
await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false);
await client.ApiClient.AddReactionAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false);
}

public static async Task AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options)
@@ -94,16 +132,31 @@ namespace Discord.Rest
await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false);
}

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

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

public static async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, BaseDiscordClient client, RequestOptions options)
{
await client.ApiClient.RemoveAllReactionsAsync(channelId, messageId, options).ConfigureAwait(false);
}

public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
{
await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false);
}

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

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


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

@@ -58,6 +58,8 @@ namespace Discord.Rest
public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>();
/// <inheritdoc />
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();
/// <inheritdoc />
public virtual IReadOnlyCollection<Sticker> Stickers => ImmutableArray.Create<Sticker>();

/// <inheritdoc />
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);
@@ -173,6 +175,8 @@ namespace Discord.Rest
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds;
/// <inheritdoc />
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray();
/// <inheritdoc />
IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers;

/// <inheritdoc />
public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me });


+ 17
- 3
src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs View File

@@ -21,6 +21,7 @@ namespace Discord.Rest
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>();
private ImmutableArray<ulong> _roleMentionIds = ImmutableArray.Create<ulong>();
private ImmutableArray<RestUser> _userMentions = ImmutableArray.Create<RestUser>();
private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>();

/// <inheritdoc />
public override bool IsTTS => _isTTS;
@@ -45,6 +46,8 @@ namespace Discord.Rest
/// <inheritdoc />
public override IReadOnlyCollection<ITag> Tags => _tags;
/// <inheritdoc />
public override IReadOnlyCollection<Sticker> Stickers => _stickers;
/// <inheritdoc />
public IUserMessage ReferencedMessage => _referencedMessage;

internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source)
@@ -132,6 +135,20 @@ namespace Discord.Rest
IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable());
_referencedMessage = RestUserMessage.Create(Discord, Channel, refMsgAuthor, refMsg);
}

if (model.Stickers.IsSpecified)
{
var value = model.Stickers.Value;
if (value.Length > 0)
{
var stickers = ImmutableArray.CreateBuilder<Sticker>(value.Length);
for (int i = 0; i < value.Length; i++)
stickers.Add(Sticker.Create(value[i]));
_stickers = stickers.ToImmutable();
}
else
_stickers = ImmutableArray.Create<Sticker>();
}
}

/// <inheritdoc />
@@ -147,9 +164,6 @@ namespace Discord.Rest
/// <inheritdoc />
public Task UnpinAsync(RequestOptions options = null)
=> MessageHelper.UnpinAsync(this, Discord, options);
/// <inheritdoc />
public Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null)
=> MessageHelper.SuppressEmbedsAsync(this, Discord, suppressEmbeds, options);

public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)


+ 48
- 0
src/Discord.Net.Rest/Entities/Messages/Sticker.cs View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Diagnostics;
using Model = Discord.API.Sticker;

namespace Discord
{
/// <inheritdoc cref="ISticker"/>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class Sticker : ISticker
{
/// <inheritdoc />
public ulong Id { get; }
/// <inheritdoc />
public ulong PackId { get; }
/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public string Description { get; }
/// <inheritdoc />
public IReadOnlyCollection<string> Tags { get; }
/// <inheritdoc />
public string Asset { get; }
/// <inheritdoc />
public string PreviewAsset { get; }
/// <inheritdoc />
public StickerFormatType FormatType { get; }

internal Sticker(ulong id, ulong packId, string name, string description, string[] tags, string asset, string previewAsset, StickerFormatType formatType)
{
Id = id;
PackId = packId;
Name = name;
Description = description;
Tags = tags.ToReadOnlyCollection();
Asset = asset;
PreviewAsset = previewAsset;
FormatType = formatType;
}
internal static Sticker Create(Model model)
{
return new Sticker(model.Id, model.PackId, model.Name, model.Desription,
model.Tags.IsSpecified ? model.Tags.Value.Split(',') : new string[0],
model.Asset, model.PreviewAsset, model.FormatType);
}

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

+ 2
- 2
src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using Model = Discord.API.Role;
using BulkParams = Discord.API.Rest.ModifyGuildRolesParams;
@@ -24,7 +24,7 @@ namespace Discord.Rest
Hoist = args.Hoist,
Mentionable = args.Mentionable,
Name = args.Name,
Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue : Optional.Create<ulong>()
Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue.ToString() : Optional.Create<string>()
};
var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options).ConfigureAwait(false);



+ 16
- 4
src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs View File

@@ -112,17 +112,29 @@ namespace Discord.Rest
public Task KickAsync(string reason = null, RequestOptions options = null)
=> UserHelper.KickAsync(this, Discord, reason, options);
/// <inheritdoc />
public Task AddRoleAsync(ulong roleId, RequestOptions options = null)
=> AddRolesAsync(new[] { roleId }, options);
/// <inheritdoc />
public Task AddRoleAsync(IRole role, RequestOptions options = null)
=> AddRolesAsync(new[] { role }, options);
=> AddRoleAsync(role.Id, options);
/// <inheritdoc />
public Task AddRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null)
=> UserHelper.AddRolesAsync(this, Discord, roleIds, options);
/// <inheritdoc />
public Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null)
=> UserHelper.AddRolesAsync(this, Discord, roles, options);
=> AddRolesAsync(roles.Select(x => x.Id), options);
/// <inheritdoc />
public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null)
=> RemoveRolesAsync(new[] { roleId }, options);
/// <inheritdoc />
public Task RemoveRoleAsync(IRole role, RequestOptions options = null)
=> RemoveRolesAsync(new[] { role }, options);
=> RemoveRoleAsync(role.Id, options);
/// <inheritdoc />
public Task RemoveRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null)
=> UserHelper.RemoveRolesAsync(this, Discord, roleIds, options);
/// <inheritdoc />
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null)
=> UserHelper.RemoveRolesAsync(this, Discord, roles, options);
=> RemoveRolesAsync(roles.Select(x => x.Id));

/// <inheritdoc />
/// <exception cref="InvalidOperationException">Resolving permissions requires the parent guild to be downloaded.</exception>


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

@@ -79,13 +79,13 @@ namespace Discord.Rest
}

/// <summary>
/// Returns a direct message channel to this user, or create one if it does not already exist.
/// Creates a direct message channel to this user.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a rest DM channel where the user is the recipient.
/// </returns>
public Task<RestDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null)
public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null)
=> UserHelper.CreateDMChannelAsync(this, Discord, options);

/// <inheritdoc />
@@ -107,7 +107,7 @@ namespace Discord.Rest

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

+ 18
- 10
src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs View File

@@ -59,27 +59,35 @@ namespace Discord.Rest
/// <inheritdoc />
ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue);
/// <inheritdoc />
Task IGuildUser.KickAsync(string reason, RequestOptions options) =>
Task IGuildUser.KickAsync(string reason, RequestOptions options) =>
throw new NotSupportedException("Webhook users cannot be kicked.");

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

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

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

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

/// <inheritdoc />
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.");

//IVoiceState


+ 6
- 6
src/Discord.Net.Rest/Entities/Users/UserHelper.cs View File

@@ -73,16 +73,16 @@ namespace Discord.Rest
return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args, options).ConfigureAwait(false));
}

public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable<IRole> roles, RequestOptions options)
public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable<ulong> roleIds, RequestOptions options)
{
foreach (var role in roles)
await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false);
foreach (var roleId in roleIds)
await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false);
}

public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable<IRole> roles, RequestOptions options)
public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable<ulong> roleIds, RequestOptions options)
{
foreach (var role in roles)
await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false);
foreach (var roleId in roleIds)
await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false);
}
}
}

+ 4
- 2
src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs View File

@@ -11,11 +11,11 @@ namespace Discord.Rest
internal IGuild Guild { get; private set; }
internal ITextChannel Channel { get; private set; }

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

/// <inheritdoc />
public ulong ChannelId { get; private set; }
/// <inheritdoc />
public string Name { get; private set; }
/// <inheritdoc />
@@ -56,6 +56,8 @@ namespace Discord.Rest

internal void Update(Model model)
{
if (ChannelId != model.ChannelId)
ChannelId = model.ChannelId;
if (model.Avatar.IsSpecified)
AvatarId = model.Avatar.Value;
if (model.Creator.IsSpecified)


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

@@ -73,8 +73,6 @@ namespace Discord.Net.Converters
}

//Enums
if (type == typeof(PermissionTarget))
return PermissionTargetConverter.Instance;
if (type == typeof(UserStatus))
return UserStatusConverter.Instance;
if (type == typeof(EmbedType))


+ 0
- 44
src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs View File

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

namespace Discord.Net.Converters
{
internal class PermissionTargetConverter : JsonConverter
{
public static readonly PermissionTargetConverter Instance = new PermissionTargetConverter();

public override bool CanConvert(Type objectType) => true;
public override bool CanRead => true;
public override bool CanWrite => true;

/// <exception cref="JsonSerializationException">Unknown permission target.</exception>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch ((string)reader.Value)
{
case "member":
return PermissionTarget.User;
case "role":
return PermissionTarget.Role;
default:
throw new JsonSerializationException("Unknown permission target.");
}
}

/// <exception cref="JsonSerializationException">Invalid permission target.</exception>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
switch ((PermissionTarget)value)
{
case PermissionTarget.User:
writer.WriteValue("member");
break;
case PermissionTarget.Role:
writer.WriteValue("role");
break;
default:
throw new JsonSerializationException("Invalid permission target.");
}
}
}
}

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

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


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

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;

namespace Discord.API.Gateway
{
@@ -12,5 +12,7 @@ namespace Discord.API.Gateway
public ulong ChannelId { get; set; }
[JsonProperty("emoji")]
public Emoji Emoji { get; set; }
[JsonProperty("member")]
public Optional<GuildMember> Member { get; set; }
}
}

+ 18
- 23
src/Discord.Net.WebSocket/BaseSocketClient.cs View File

@@ -70,20 +70,11 @@ namespace Discord.WebSocket
/// A read-only collection of private channels that the user currently partakes in.
/// </returns>
public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; }
/// <summary>
/// Gets a collection of available voice regions.
/// </summary>
/// <returns>
/// A read-only collection of voice regions that the user has access to.
/// </returns>
[Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")]
public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; }

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

/// <summary>
@@ -164,16 +155,6 @@ namespace Discord.WebSocket
/// </returns>
public abstract SocketGuild GetGuild(ulong id);
/// <summary>
/// Gets a voice region.
/// </summary>
/// <param name="id">The identifier of the voice region (e.g. <c>eu-central</c> ).</param>
/// <returns>
/// A REST-based voice region associated with the identifier; <c>null</c> if the voice region is not
/// found.
/// </returns>
[Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")]
public abstract RestVoiceRegion GetVoiceRegion(string id);
/// <summary>
/// Gets all voice regions.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
@@ -209,6 +190,12 @@ namespace Discord.WebSocket
/// <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>
/// <remarks>
/// <note type="warning">
/// Bot accounts cannot set <see cref="ActivityType.CustomStatus"/> as their activity
/// type and it will have no effect.
/// </note>
/// </remarks>
/// <returns>
/// A task that represents the asynchronous set operation.
/// </returns>
@@ -222,6 +209,10 @@ namespace Discord.WebSocket
/// Discord will only accept setting of name and the type of activity.
/// </note>
/// <note type="warning">
/// Bot accounts cannot set <see cref="ActivityType.CustomStatus"/> as their activity
/// type and it will have no effect.
/// </note>
/// <note type="warning">
/// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC
/// clients only.
/// </note>
@@ -317,10 +308,14 @@ namespace Discord.WebSocket
=> Task.FromResult<IUser>(GetUser(username, discriminator));

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

+ 9
- 14
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

@@ -36,9 +36,6 @@ namespace Discord.WebSocket
/// <inheritdoc />
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount);
public IReadOnlyCollection<DiscordSocketClient> Shards => _shards;
/// <inheritdoc />
[Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")]
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions;

/// <summary>
/// Provides access to a REST-only client with a shared state from this client.
@@ -91,8 +88,7 @@ namespace Discord.WebSocket
}
}
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent,
rateLimitPrecision: config.RateLimitPrecision);
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent);

internal async Task AcquireIdentifyLockAsync(int shardId, CancellationToken token)
{
@@ -264,11 +260,6 @@ namespace Discord.WebSocket
return null;
}

/// <inheritdoc />
[Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")]
public override RestVoiceRegion GetVoiceRegion(string id)
=> _shards[0].GetVoiceRegion(id);

/// <inheritdoc />
public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null)
{
@@ -432,11 +423,15 @@ namespace Discord.WebSocket
=> Task.FromResult<IUser>(GetUser(username, discriminator));

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

internal override void Dispose(bool disposing)
{


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

@@ -40,9 +40,8 @@ namespace Discord.API

public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent,
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null,
RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second,
bool useSystemClock = true)
: base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision, useSystemClock)
: base(restClientProvider, userAgent, defaultRetryMode, serializer, useSystemClock)
{
_gatewayUrl = url;
if (url != null)
@@ -216,7 +215,7 @@ namespace Discord.API
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false);
}

public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, bool guildSubscriptions = true, GatewayIntents? gatewayIntents = null, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null)
public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, GatewayIntents gatewayIntents = GatewayIntents.AllUnprivileged, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);
var props = new Dictionary<string, string>
@@ -234,10 +233,7 @@ namespace Discord.API

options.BucketId = GatewayBucket.Get(GatewayBucketType.Identify).Id;

if (gatewayIntents.HasValue)
msg.Intents = (int)gatewayIntents.Value;
else
msg.GuildSubscriptions = guildSubscriptions;
msg.Intents = (int)gatewayIntents;

if (presence.HasValue)
{


+ 80
- 70
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -43,8 +43,7 @@ namespace Discord.WebSocket
private DateTimeOffset? _statusSince;
private RestApplication _applicationInfo;
private bool _isDisposed;
private bool _guildSubscriptions;
private GatewayIntents? _gatewayIntents;
private GatewayIntents _gatewayIntents;

/// <summary>
/// Provides access to a REST-only client with a shared state from this client.
@@ -109,9 +108,6 @@ namespace Discord.WebSocket
/// </returns>
public IReadOnlyCollection<SocketGroupChannel> GroupChannels
=> State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray();
/// <inheritdoc />
[Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")]
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => GetVoiceRegionsAsync().GetAwaiter().GetResult();

/// <summary>
/// Initializes a new REST/WebSocket-based Discord client.
@@ -140,7 +136,6 @@ namespace Discord.WebSocket
State = new ClientState(0, 0);
Rest = new DiscordSocketRestClient(config, ApiClient);
_heartbeatTimes = new ConcurrentQueue<long>();
_guildSubscriptions = config.GuildSubscriptions;
_gatewayIntents = config.GatewayIntents;

_stateLock = new SemaphoreSlim(1, 1);
@@ -182,8 +177,7 @@ namespace Discord.WebSocket
_largeGuilds = new ConcurrentQueue<ulong>();
}
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost,
rateLimitPrecision: config.RateLimitPrecision);
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost);
/// <inheritdoc />
internal override void Dispose(bool disposing)
{
@@ -243,7 +237,7 @@ namespace Discord.WebSocket
else
{
await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false);
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false);
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false);
}
}
finally
@@ -308,7 +302,7 @@ namespace Discord.WebSocket
/// <summary>
/// Clears cached DM channels from the client.
/// </summary>
public void PurgeDMChannelCache() => State.PurgeDMChannels();
public void PurgeDMChannelCache() => RemoveDMChannels();

/// <inheritdoc />
public override SocketUser GetUser(ulong id)
@@ -322,12 +316,11 @@ namespace Discord.WebSocket
public void PurgeUserCache() => State.PurgeUsers();
internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model)
{
return state.GetOrAddUser(model.Id, x =>
{
var user = SocketGlobalUser.Create(this, state, model);
user.GlobalUser.AddRef();
return user;
});
return state.GetOrAddUser(model.Id, x => SocketGlobalUser.Create(this, state, model));
}
internal SocketUser GetOrCreateTemporaryUser(ClientState state, Discord.API.User model)
{
return state.GetUser(model.Id) ?? (SocketUser)SocketUnknownUser.Create(this, state, model);
}
internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model)
{
@@ -335,18 +328,13 @@ namespace Discord.WebSocket
{
var user = SocketGlobalUser.Create(this, state, model);
user.GlobalUser.AddRef();
user.Presence = new SocketPresence(UserStatus.Online, null, null, null);
user.Presence = new SocketPresence(UserStatus.Online, null, null);
return user;
});
}
internal void RemoveUser(ulong id)
=> State.RemoveUser(id);

/// <inheritdoc />
[Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")]
public override RestVoiceRegion GetVoiceRegion(string id)
=> GetVoiceRegionAsync(id).GetAwaiter().GetResult();

/// <inheritdoc />
public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null)
{
@@ -469,7 +457,8 @@ namespace Discord.WebSocket
{
if (CurrentUser == null)
return;
CurrentUser.Presence = new SocketPresence(Status, Activity, null, null);
var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null;
CurrentUser.Presence = new SocketPresence(Status, null, activities);

var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null);

@@ -564,7 +553,7 @@ namespace Discord.WebSocket
await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false);
try
{
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false);
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false);
}
finally
{
@@ -572,7 +561,7 @@ namespace Discord.WebSocket
}
}
else
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false);
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false);
}
break;
case GatewayOpCode.Reconnect:
@@ -595,7 +584,8 @@ namespace Discord.WebSocket
var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length);

var currentUser = SocketSelfUser.Create(this, state, data.User);
currentUser.Presence = new SocketPresence(Status, Activity, null, null);
var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null;
currentUser.Presence = new SocketPresence(Status, null, activities);
ApiClient.CurrentUserId = currentUser.Id;
int unavailableGuilds = 0;
for (int i = 0; i < data.Guilds.Length; i++)
@@ -1237,56 +1227,63 @@ namespace Discord.WebSocket
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Message>(_serializer);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel;

var guild = (channel as SocketGuildChannel)?.Guild;
if (guild != null && !guild.IsSynced)
{
var guild = (channel as SocketGuildChannel)?.Guild;
if (guild != null && !guild.IsSynced)
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false);
return;
}

if (channel == null)
{
if (!data.GuildId.IsSpecified) // assume it is a DM
{
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false);
return;
channel = CreateDMChannel(data.ChannelId, data.Author.Value, State);
}

SocketUser author;
if (guild != null)
else
{
if (data.WebhookId.IsSpecified)
author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value);
else
author = guild.GetUser(data.Author.Value.Id);
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false);
return;
}
}

SocketUser author;
if (guild != null)
{
if (data.WebhookId.IsSpecified)
author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value);
else
author = (channel as SocketChannel).GetUser(data.Author.Value.Id);
author = guild.GetUser(data.Author.Value.Id);
}
else
author = (channel as SocketChannel).GetUser(data.Author.Value.Id);

if (author == null)
if (author == null)
{
if (guild != null)
{
if (guild != null)
if (data.Member.IsSpecified) // member isn't always included, but use it when we can
{
if (data.Member.IsSpecified) // member isn't always included, but use it when we can
{
data.Member.Value.User = data.Author.Value;
author = guild.AddOrUpdateUser(data.Member.Value);
}
else
author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data
data.Member.Value.User = data.Author.Value;
author = guild.AddOrUpdateUser(data.Member.Value);
}
else if (channel is SocketGroupChannel)
author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value);
else
{
await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false);
return;
}
author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data
}
else if (channel is SocketGroupChannel groupChannel)
author = groupChannel.GetOrAddUser(data.Author.Value);
else
{
await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false);
return;
}

var msg = SocketMessage.Create(this, State, author, channel, data);
SocketChannelHelper.AddMessage(channel, this, msg);
await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false);
}
else
{
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false);
return;
}

var msg = SocketMessage.Create(this, State, author, channel, data);
SocketChannelHelper.AddMessage(channel, this, msg);
await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false);
}
break;
case "MESSAGE_UPDATE":
@@ -1384,6 +1381,14 @@ namespace Discord.WebSocket
? Optional.Create<SocketUserMessage>()
: Optional.Create(cachedMsg);

if (data.Member.IsSpecified)
{
var guild = (channel as SocketGuildChannel)?.Guild;
if (guild != null)
user = guild.AddOrUpdateUser(data.Member.Value);
}

var optionalUser = user is null
? Optional.Create<IUser>()
: Optional.Create(user);
@@ -1474,7 +1479,7 @@ namespace Discord.WebSocket
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage);
var emote = data.Emoji.ToIEmote();

cachedMsg?.RemoveAllReactionsForEmoteAsync(emote);
cachedMsg?.RemoveReactionsForEmote(emote);

await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false);
}
@@ -1930,24 +1935,29 @@ namespace Discord.WebSocket
{
var channel = SocketChannel.CreatePrivate(this, state, model);
state.AddChannel(channel as SocketChannel);
if (channel is SocketDMChannel dm)
dm.Recipient.GlobalUser.DMChannel = dm;

return channel;
}
internal SocketDMChannel CreateDMChannel(ulong channelId, API.User model, ClientState state)
{
return SocketDMChannel.Create(this, state, channelId, model);
}
internal ISocketPrivateChannel RemovePrivateChannel(ulong id)
{
var channel = State.RemoveChannel(id) as ISocketPrivateChannel;
if (channel != null)
{
if (channel is SocketDMChannel dmChannel)
dmChannel.Recipient.GlobalUser.DMChannel = null;

foreach (var recipient in channel.Recipients)
recipient.GlobalUser.RemoveRef(this);
}
return channel;
}
internal void RemoveDMChannels()
{
var channels = State.DMChannels;
State.PurgeDMChannels();
foreach (var channel in channels)
channel.Recipient.GlobalUser.RemoveRef(this);
}

private async Task GuildAvailableAsync(SocketGuild guild)
{


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

@@ -127,12 +127,6 @@ namespace Discord.WebSocket
/// </remarks>
public bool? ExclusiveBulkDelete { get; set; } = null;

/// <summary>
/// Gets or sets enabling dispatching of guild subscription events e.g. presence and typing events.
/// This is not used if <see cref="GatewayIntents"/> are provided.
/// </summary>
public bool GuildSubscriptions { get; set; } = true;

/// <summary>
/// Gets or sets the maximum identify concurrency.
/// </summary>
@@ -172,14 +166,15 @@ namespace Discord.WebSocket
private int maxWaitForGuildAvailable = 10000;

/// <summary>
/// Gets or sets gateway intents to limit what events are sent from Discord. Allows for more granular control than the <see cref="GuildSubscriptions"/> property.
/// Gets or sets gateway intents to limit what events are sent from Discord.
/// The default is <see cref="GatewayIntents.AllUnprivileged"/>.
/// </summary>
/// <remarks>
/// For more information, please see
/// <see href="https://discord.com/developers/docs/topics/gateway#gateway-intents">GatewayIntents</see>
/// on the official Discord API documentation.
/// </remarks>
public GatewayIntents? GatewayIntents { get; set; }
public GatewayIntents GatewayIntents { get; set; } = GatewayIntents.AllUnprivileged;

/// <summary>
/// Initializes a new instance of the <see cref="DiscordSocketConfig"/> class with the default configuration.


+ 33
- 24
src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs View File

@@ -16,32 +16,27 @@ namespace Discord.WebSocket
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel
{
private readonly MessageCache _messages;

/// <summary>
/// Gets the recipient of the channel.
/// </summary>
public SocketUser Recipient { get; }

/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>();
public IReadOnlyCollection<SocketMessage> CachedMessages => ImmutableArray.Create<SocketMessage>();

/// <summary>
/// Gets a collection that is the current logged-in user and the recipient.
/// </summary>
public new IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient);

internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketGlobalUser recipient)
internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketUser recipient)
: base(discord, id)
{
Recipient = recipient;
recipient.GlobalUser.AddRef();
if (Discord.MessageCacheSize > 0)
_messages = new MessageCache(Discord);
}
internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model)
{
var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0]));
var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateTemporaryUser(state, model.Recipients.Value[0]));
entity.Update(state, model);
return entity;
}
@@ -49,6 +44,16 @@ namespace Discord.WebSocket
{
Recipient.Update(state, model.Recipients.Value[0]);
}
internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, ulong channelId, API.User recipient)
{
var entity = new SocketDMChannel(discord, channelId, discord.GetOrCreateTemporaryUser(state, recipient));
entity.Update(state, recipient);
return entity;
}
internal void Update(ClientState state, API.User recipient)
{
Recipient.Update(state, recipient);
}

/// <inheritdoc />
public Task CloseAsync(RequestOptions options = null)
@@ -57,7 +62,7 @@ namespace Discord.WebSocket
//Messages
/// <inheritdoc />
public SocketMessage GetCachedMessage(ulong id)
=> _messages?.Get(id);
=> null;
/// <summary>
/// Gets the message associated with the given <paramref name="id"/>.
/// </summary>
@@ -68,10 +73,7 @@ namespace Discord.WebSocket
/// </returns>
public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null)
{
IMessage msg = _messages?.Get(id);
if (msg == null)
msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false);
return msg;
return await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false);
}

/// <summary>
@@ -87,7 +89,7 @@ namespace Discord.WebSocket
/// Paged collection of messages.
/// </returns>
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
/// <summary>
/// Gets a collection of messages in this channel.
/// </summary>
@@ -103,7 +105,7 @@ namespace Discord.WebSocket
/// Paged collection of messages.
/// </returns>
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
/// <summary>
/// Gets a collection of messages in this channel.
/// </summary>
@@ -119,16 +121,16 @@ namespace Discord.WebSocket
/// Paged collection of messages.
/// </returns>
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options);
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit);
=> ImmutableArray.Create<SocketMessage>();
/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit);
=> ImmutableArray.Create<SocketMessage>();
/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit);
=> ImmutableArray.Create<SocketMessage>();
/// <inheritdoc />
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
@@ -152,6 +154,10 @@ namespace Discord.WebSocket
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);

/// <inheritdoc />
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);

/// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
@@ -160,9 +166,12 @@ namespace Discord.WebSocket
=> ChannelHelper.EnterTypingState(this, Discord, options);

internal void AddMessage(SocketMessage msg)
=> _messages?.Add(msg);
{
}
internal SocketMessage RemoveMessage(ulong id)
=> _messages?.Remove(id);
{
return null;
}

//Users
/// <summary>
@@ -218,13 +227,13 @@ namespace Discord.WebSocket
}
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options);
=> mode == CacheMode.CacheOnly ? null : GetMessagesAsync(limit, options);
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options);
=> mode == CacheMode.CacheOnly ? null : GetMessagesAsync(fromMessageId, dir, limit, options);
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options);
=> mode == CacheMode.CacheOnly ? null : GetMessagesAsync(fromMessage.Id, dir, limit, options);
/// <inheritdoc />
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);


+ 4
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs View File

@@ -180,6 +180,10 @@ namespace Discord.WebSocket
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);

/// <inheritdoc />
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);

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


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

@@ -180,6 +180,10 @@ namespace Discord.WebSocket
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

/// <inheritdoc />
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);

/// <inheritdoc />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);


+ 3
- 35
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -46,8 +46,6 @@ namespace Discord.WebSocket
/// <inheritdoc />
public int AFKTimeout { get; private set; }
/// <inheritdoc />
public bool IsEmbeddable { get; private set; }
/// <inheritdoc />
public bool IsWidgetEnabled { get; private set; }
/// <inheritdoc />
public VerificationLevel VerificationLevel { get; private set; }
@@ -84,7 +82,6 @@ namespace Discord.WebSocket
public ulong? ApplicationId { get; internal set; }

internal ulong? AFKChannelId { get; private set; }
internal ulong? EmbedChannelId { get; private set; }
internal ulong? WidgetChannelId { get; private set; }
internal ulong? SystemChannelId { get; private set; }
internal ulong? RulesChannelId { get; private set; }
@@ -198,21 +195,6 @@ namespace Discord.WebSocket
}
}
/// <summary>
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary>
/// <returns>
/// A channel set within the server's widget settings; <see langword="null"/> if none is set.
/// </returns>
[Obsolete("This property is deprecated, use WidgetChannel instead.")]
public SocketGuildChannel EmbedChannel
{
get
{
var id = EmbedChannelId;
return id.HasValue ? GetChannel(id.Value) : null;
}
}
/// <summary>
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild.
/// </summary>
/// <returns>
@@ -440,16 +422,12 @@ namespace Discord.WebSocket
internal void Update(ClientState state, Model model)
{
AFKChannelId = model.AFKChannelId;
if (model.EmbedChannelId.IsSpecified)
EmbedChannelId = model.EmbedChannelId.Value;
if (model.WidgetChannelId.IsSpecified)
WidgetChannelId = model.WidgetChannelId.Value;
SystemChannelId = model.SystemChannelId;
RulesChannelId = model.RulesChannelId;
PublicUpdatesChannelId = model.PublicUpdatesChannelId;
AFKTimeout = model.AFKTimeout;
if (model.EmbedEnabled.IsSpecified)
IsEmbeddable = model.EmbedEnabled.Value;
if (model.WidgetEnabled.IsSpecified)
IsWidgetEnabled = model.WidgetEnabled.Value;
IconId = model.Icon;
@@ -548,11 +526,6 @@ namespace Discord.WebSocket

/// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")]
public Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null)
=> GuildHelper.ModifyEmbedAsync(this, Discord, func, options);
/// <inheritdoc />
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
public Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null)
=> GuildHelper.ModifyWidgetAsync(this, Discord, func, options);
/// <inheritdoc />
@@ -1015,6 +988,9 @@ namespace Discord.WebSocket

//Emotes
/// <inheritdoc />
public Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null)
=> GuildHelper.GetEmotesAsync(this, Discord, options);
/// <inheritdoc />
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetEmoteAsync(this, Discord, id, options);
/// <inheritdoc />
@@ -1236,10 +1212,6 @@ namespace Discord.WebSocket
/// <inheritdoc />
bool IGuild.Available => true;
/// <inheritdoc />
ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0;
/// <inheritdoc />
ulong? IGuild.EmbedChannelId => EmbedChannelId;
/// <inheritdoc />
ulong? IGuild.WidgetChannelId => WidgetChannelId;
/// <inheritdoc />
ulong? IGuild.SystemChannelId => SystemChannelId;
@@ -1294,10 +1266,6 @@ namespace Discord.WebSocket
Task<ITextChannel> IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<ITextChannel>(DefaultChannel);
/// <inheritdoc />
[Obsolete("This method is deprecated, use GetWidgetChannelAsync instead.")]
Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuildChannel>(EmbedChannel);
/// <inheritdoc />
Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuildChannel>(WidgetChannel);
/// <inheritdoc />


+ 0
- 3
src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs View File

@@ -49,9 +49,6 @@ namespace Discord.WebSocket
/// <inheritdoc />
int? IInvite.MemberCount => throw new NotImplementedException();
/// <inheritdoc />
[Obsolete("This property doesn't exist anymore and shouldn't be used.")]
bool IInviteMetadata.IsRevoked => throw new NotImplementedException();
/// <inheritdoc />
public bool IsTemporary { get; private set; }
/// <inheritdoc />
int? IInviteMetadata.MaxAge { get => MaxAge; }


+ 4
- 0
src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs View File

@@ -99,6 +99,8 @@ namespace Discord.WebSocket
/// <inheritdoc />
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();
/// <inheritdoc />
public virtual IReadOnlyCollection<Sticker> Stickers => ImmutableArray.Create<Sticker>();
/// <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) });

/// <inheritdoc />
@@ -194,6 +196,8 @@ namespace Discord.WebSocket
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray();
/// <inheritdoc />
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray();
/// <inheritdoc />
IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers;

internal void AddReaction(SocketReaction reaction)
{


+ 17
- 3
src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs View File

@@ -23,6 +23,7 @@ namespace Discord.WebSocket
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>();
private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>();
private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>();
private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>();

/// <inheritdoc />
public override bool IsTTS => _isTTS;
@@ -47,6 +48,8 @@ namespace Discord.WebSocket
/// <inheritdoc />
public override IReadOnlyCollection<SocketUser> MentionedUsers => _userMentions;
/// <inheritdoc />
public override IReadOnlyCollection<Sticker> Stickers => _stickers;
/// <inheritdoc />
public IUserMessage ReferencedMessage => _referencedMessage;

internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source)
@@ -158,6 +161,20 @@ namespace Discord.WebSocket
refMsgAuthor = new SocketUnknownUser(Discord, id: 0);
_referencedMessage = SocketUserMessage.Create(Discord, state, refMsgAuthor, Channel, refMsg);
}

if (model.Stickers.IsSpecified)
{
var value = model.Stickers.Value;
if (value.Length > 0)
{
var stickers = ImmutableArray.CreateBuilder<Sticker>(value.Length);
for (int i = 0; i < value.Length; i++)
stickers.Add(Sticker.Create(value[i]));
_stickers = stickers.ToImmutable();
}
else
_stickers = ImmutableArray.Create<Sticker>();
}
}

/// <inheritdoc />
@@ -172,9 +189,6 @@ namespace Discord.WebSocket
/// <inheritdoc />
public Task UnpinAsync(RequestOptions options = null)
=> MessageHelper.UnpinAsync(this, Discord, options);
/// <inheritdoc />
public Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null)
=> MessageHelper.SuppressEmbedsAsync(this, Discord, suppressEmbeds, options);

public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)


+ 0
- 2
src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs View File

@@ -12,7 +12,6 @@ namespace Discord.WebSocket
public override string Username { get; internal set; }
public override ushort DiscriminatorValue { get; internal set; }
public override string AvatarId { get; internal set; }
public SocketDMChannel DMChannel { get; internal set; }
internal override SocketPresence Presence { get; set; }

public override bool IsWebhook => false;
@@ -52,7 +51,6 @@ namespace Discord.WebSocket
internal void Update(ClientState state, PresenceModel model)
{
Presence = SocketPresence.Create(model);
DMChannel = state.DMChannels.FirstOrDefault(x => x.Recipient.Id == Id);
}

private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)";


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

@@ -63,7 +63,7 @@ namespace Discord.WebSocket
/// <summary>
/// Returns a collection of roles that the user possesses.
/// </summary>
public IReadOnlyCollection<SocketRole> Roles
public IReadOnlyCollection<SocketRole> Roles
=> _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length);
/// <summary>
/// Returns the voice channel the user is in, or <c>null</c> if none.
@@ -177,17 +177,29 @@ namespace Discord.WebSocket
public Task KickAsync(string reason = null, RequestOptions options = null)
=> UserHelper.KickAsync(this, Discord, reason, options);
/// <inheritdoc />
public Task AddRoleAsync(ulong roleId, RequestOptions options = null)
=> AddRolesAsync(new[] { roleId }, options);
/// <inheritdoc />
public Task AddRoleAsync(IRole role, RequestOptions options = null)
=> AddRolesAsync(new[] { role }, options);
=> AddRoleAsync(role.Id, options);
/// <inheritdoc />
public Task AddRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null)
=> UserHelper.AddRolesAsync(this, Discord, roleIds, options);
/// <inheritdoc />
public Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null)
=> UserHelper.AddRolesAsync(this, Discord, roles, options);
=> AddRolesAsync(roles.Select(x => x.Id), options);
/// <inheritdoc />
public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null)
=> RemoveRolesAsync(new[] { roleId }, options);
/// <inheritdoc />
public Task RemoveRoleAsync(IRole role, RequestOptions options = null)
=> RemoveRolesAsync(new[] { role }, options);
=> RemoveRoleAsync(role.Id, options);
/// <inheritdoc />
public Task RemoveRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null)
=> UserHelper.RemoveRolesAsync(this, Discord, roleIds, options);
/// <inheritdoc />
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null)
=> UserHelper.RemoveRolesAsync(this, Discord, roles, options);
=> RemoveRolesAsync(roles.Select(x => x.Id));

/// <inheritdoc />
public ChannelPermissions GetPermissions(IGuildChannel channel)


+ 4
- 6
src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Model = Discord.API.Presence;

namespace Discord.WebSocket
@@ -15,15 +16,12 @@ namespace Discord.WebSocket
/// <inheritdoc />
public UserStatus Status { get; }
/// <inheritdoc />
public IActivity Activity { get; }
/// <inheritdoc />
public IImmutableSet<ClientType> ActiveClients { get; }
/// <inheritdoc />
public IImmutableList<IActivity> Activities { get; }
internal SocketPresence(UserStatus status, IActivity activity, IImmutableSet<ClientType> activeClients, IImmutableList<IActivity> activities)
internal SocketPresence(UserStatus status, IImmutableSet<ClientType> activeClients, IImmutableList<IActivity> activities)
{
Status = status;
Activity = activity;
ActiveClients = activeClients ?? ImmutableHashSet<ClientType>.Empty;
Activities = activities ?? ImmutableList<IActivity>.Empty;
}
@@ -31,7 +29,7 @@ namespace Discord.WebSocket
{
var clients = ConvertClientTypesDict(model.ClientStatus.GetValueOrDefault());
var activities = ConvertActivitiesList(model.Activities);
return new SocketPresence(model.Status, model.Game?.ToEntity(), clients, activities);
return new SocketPresence(model.Status, clients, activities);
}
/// <summary>
/// Creates a new <see cref="IReadOnlyCollection{T}"/> containing all of the client types
@@ -84,7 +82,7 @@ namespace Discord.WebSocket
/// A string that resolves to <see cref="Discord.WebSocket.SocketPresence.Status" />.
/// </returns>
public override string ToString() => Status.ToString();
private string DebuggerDisplay => $"{Status}{(Activity != null ? $", {Activity.Name}": "")}";
private string DebuggerDisplay => $"{Status}{(Activities?.FirstOrDefault()?.Name ?? "")}";

internal SocketPresence Clone() => this;
}


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

@@ -25,7 +25,7 @@ namespace Discord.WebSocket
/// <inheritdoc />
public override bool IsWebhook => false;
/// <inheritdoc />
internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null, null); } set { } }
internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null); } set { } }
/// <inheritdoc />
/// <exception cref="NotSupportedException">This field is not supported for an unknown user.</exception>
internal override SocketGlobalUser GlobalUser =>


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

@@ -38,8 +38,6 @@ namespace Discord.WebSocket
/// <inheritdoc />
public string Mention => MentionUtils.MentionUser(Id);
/// <inheritdoc />
public IActivity Activity => Presence.Activity;
/// <inheritdoc />
public UserStatus Status => Presence.Status;
/// <inheritdoc />
public IImmutableSet<ClientType> ActiveClients => Presence.ActiveClients ?? ImmutableHashSet<ClientType>.Empty;
@@ -94,8 +92,8 @@ namespace Discord.WebSocket
}

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

/// <inheritdoc />
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)


+ 28
- 8
src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs View File

@@ -30,8 +30,8 @@ namespace Discord.WebSocket
/// <inheritdoc />
public override bool IsWebhook => true;
/// <inheritdoc />
internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null, null); } set { } }
internal override SocketGlobalUser GlobalUser =>
internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null); } set { } }
internal override SocketGlobalUser GlobalUser =>
throw new NotSupportedException();

internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId)
@@ -73,32 +73,52 @@ namespace Discord.WebSocket
ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue);
/// <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.");

/// <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.");

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

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

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

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

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

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

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

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

//IVoiceState


+ 29
- 0
src/Discord.Net.Webhook/DiscordWebhookClient.cs View File

@@ -91,6 +91,35 @@ namespace Discord.Webhook
string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null)
=> WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options);

/// <summary>
/// Modifies a message posted using this webhook.
/// </summary>
/// <remarks>
/// This method can only modify messages that were sent using the same webhook.
/// </remarks>
/// <param name="messageId">ID of the modified message.</param>
/// <param name="func">A delegate containing the properties to modify the message with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
public Task ModifyMessageAsync(ulong messageId, Action<WebhookMessageProperties> func, RequestOptions options = null)
=> WebhookClientHelper.ModifyMessageAsync(this, messageId, func, options);

/// <summary>
/// Deletes a message posted using this webhook.
/// </summary>
/// <remarks>
/// This method can only delete messages that were sent using the same webhook.
/// </remarks>
/// <param name="messageId">ID of the deleted message.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous deletion operation.
/// </returns>
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> WebhookClientHelper.DeleteMessageAsync(this, messageId, options);

/// <summary> Sends a message to the channel for this webhook with an attachment. </summary>
/// <returns> Returns the ID of the created message. </returns>
public Task<ulong> SendFileAsync(string filePath, string text, bool isTTS = false,


+ 26
- 0
src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs View File

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

namespace Discord.Webhook
{
/// <summary>
/// Properties that are used to modify an Webhook message with the specified changes.
/// </summary>
public class WebhookMessageProperties
{
/// <summary>
/// Gets or sets the content of the message.
/// </summary>
/// <remarks>
/// This must be less than the constant defined by <see cref="DiscordConfig.MaxMessageSize"/>.
/// </remarks>
public Optional<string> Content { get; set; }
/// <summary>
/// Gets or sets the embed array that the message should display.
/// </summary>
public Optional<IEnumerable<Embed>> Embeds { get; set; }
/// <summary>
/// Gets or sets the allowed mentions of the message.
/// </summary>
public Optional<AllowedMentions> AllowedMentions { get; set; }
}
}

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

Loading…
Cancel
Save