Browse Source

Pull d.net/dev

Merged Discord.Net/dev into here
pull/1958/head
quin lynch 4 years ago
parent
commit
f368447729
52 changed files with 739 additions and 218 deletions
  1. +2
    -2
      src/Discord.Net.Commands/CommandService.cs
  2. +15
    -0
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  3. +9
    -0
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  4. +6
    -50
      src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
  5. +8
    -0
      src/Discord.Net.Core/Entities/Messages/IMessage.cs
  6. +67
    -0
      src/Discord.Net.Core/Entities/Messages/ISticker.cs
  7. +15
    -0
      src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs
  8. +36
    -1
      src/Discord.Net.Core/Entities/Users/IGuildUser.cs
  9. +1
    -1
      src/Discord.Net.Core/Utils/SnowflakeUtils.cs
  10. +1
    -1
      src/Discord.Net.Rest/API/Common/AuditLogEntry.cs
  11. +2
    -0
      src/Discord.Net.Rest/API/Common/Message.cs
  12. +25
    -0
      src/Discord.Net.Rest/API/Common/Sticker.cs
  13. +16
    -0
      src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs
  14. +10
    -6
      src/Discord.Net.Rest/ClientHelper.cs
  15. +46
    -0
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  16. +13
    -1
      src/Discord.Net.Rest/DiscordRestClient.cs
  17. +9
    -1
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs
  18. +5
    -2
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs
  19. +10
    -4
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs
  20. +10
    -4
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs
  21. +1
    -1
      src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs
  22. +7
    -0
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  23. +4
    -0
      src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
  24. +4
    -0
      src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
  25. +4
    -0
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  26. +5
    -0
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  27. +3
    -0
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  28. +62
    -0
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  29. +4
    -0
      src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
  30. +17
    -0
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  31. +48
    -0
      src/Discord.Net.Rest/Entities/Messages/Sticker.cs
  32. +16
    -4
      src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
  33. +18
    -10
      src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
  34. +6
    -6
      src/Discord.Net.Rest/Entities/Users/UserHelper.cs
  35. +4
    -2
      src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
  36. +3
    -1
      src/Discord.Net.WebSocket/API/Gateway/Reaction.cs
  37. +0
    -16
      src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
  38. +10
    -0
      src/Discord.Net.WebSocket/BaseSocketClient.cs
  39. +9
    -56
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  40. +4
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs
  41. +4
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs
  42. +4
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
  43. +3
    -18
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  44. +4
    -0
      src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
  45. +17
    -0
      src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
  46. +17
    -5
      src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
  47. +27
    -7
      src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
  48. +29
    -0
      src/Discord.Net.Webhook/DiscordWebhookClient.cs
  49. +26
    -0
      src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs
  50. +4
    -2
      src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs
  51. +53
    -1
      src/Discord.Net.Webhook/WebhookClientHelper.cs
  52. +16
    -16
      src/Discord.Net/Discord.Net.nuspec

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

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


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


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

@@ -257,6 +257,21 @@ namespace Discord
/// </returns> /// </returns>
Task DeleteMessageAsync(IMessage message, RequestOptions options = null); 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> /// <summary>
/// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds.
/// </summary> /// </summary>


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

@@ -892,6 +892,15 @@ namespace Discord
/// </returns> /// </returns>
Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); 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> /// <summary>
/// Gets a specific emote from this guild. /// Gets a specific emote from this guild.
/// </summary> /// </summary>


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

@@ -12,7 +12,6 @@ namespace Discord
{ {
private string _title; private string _title;
private string _description; private string _description;
private string _url;
private EmbedImage? _image; private EmbedImage? _image;
private EmbedThumbnail? _thumbnail; private EmbedThumbnail? _thumbnail;
private List<EmbedFieldBuilder> _fields; private List<EmbedFieldBuilder> _fields;
@@ -70,26 +69,14 @@ namespace Discord
/// <summary> Gets or sets the URL of an <see cref="Embed"/>. </summary> /// <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> /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception>
/// <returns> The URL of the embed.</returns> /// <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> /// <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> /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception>
/// <returns> The thumbnail URL of the embed.</returns> /// <returns> The thumbnail URL of the embed.</returns>
public string ThumbnailUrl public string ThumbnailUrl
{ {
get => _thumbnail?.Url; 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> /// <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> /// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception>
@@ -97,11 +84,7 @@ namespace Discord
public string ImageUrl public string ImageUrl
{ {
get => _image?.Url; 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> /// <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 public class EmbedAuthorBuilder
{ {
private string _name; private string _name;
private string _url;
private string _iconUrl;
/// <summary> /// <summary>
/// Gets the maximum author name length allowed by Discord. /// Gets the maximum author name length allowed by Discord.
/// </summary> /// </summary>
@@ -585,15 +566,7 @@ namespace Discord
/// <returns> /// <returns>
/// The URL of the author field. /// The URL of the author field.
/// </returns> /// </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> /// <summary>
/// Gets or sets the icon URL of the author field. /// Gets or sets the icon URL of the author field.
/// </summary> /// </summary>
@@ -601,15 +574,7 @@ namespace Discord
/// <returns> /// <returns>
/// The icon URL of the author field. /// The icon URL of the author field.
/// </returns> /// </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> /// <summary>
/// Sets the name of the author field. /// Sets the name of the author field.
@@ -671,7 +636,6 @@ namespace Discord
public class EmbedFooterBuilder public class EmbedFooterBuilder
{ {
private string _text; private string _text;
private string _iconUrl;


/// <summary> /// <summary>
/// Gets the maximum footer length allowed by Discord. /// Gets the maximum footer length allowed by Discord.
@@ -703,15 +667,7 @@ namespace Discord
/// <returns> /// <returns>
/// The icon URL of the footer field. /// The icon URL of the footer field.
/// </returns> /// </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> /// <summary>
/// Sets the name of the footer field. /// 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> /// </summary>
IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions { get; } 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> /// <summary>
/// Gets the flags related to this message. /// Gets the flags related to this message.
/// </summary> /// </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; }
}
}

+ 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,
}
}

+ 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. /// A task that represents the asynchronous modification operation.
/// </returns> /// </returns>
Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null); 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> /// <summary>
/// Adds the specified role to this user in the guild. /// Adds the specified role to this user in the guild.
/// </summary> /// </summary>
@@ -124,6 +132,15 @@ namespace Discord
/// </returns> /// </returns>
Task AddRoleAsync(IRole role, RequestOptions options = null); Task AddRoleAsync(IRole role, RequestOptions options = null);
/// <summary> /// <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. /// Adds the specified <paramref name="roles"/> to this user in the guild.
/// </summary> /// </summary>
/// <param name="roles">The roles to be added to the user.</param> /// <param name="roles">The roles to be added to the user.</param>
@@ -133,6 +150,15 @@ namespace Discord
/// </returns> /// </returns>
Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null); Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null);
/// <summary> /// <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. /// Removes the specified <paramref name="role"/> from this user in the guild.
/// </summary> /// </summary>
/// <param name="role">The role to be removed from the user.</param> /// <param name="role">The role to be removed from the user.</param>
@@ -142,6 +168,15 @@ namespace Discord
/// </returns> /// </returns>
Task RemoveRoleAsync(IRole role, RequestOptions options = null); Task RemoveRoleAsync(IRole role, RequestOptions options = null);
/// <summary> /// <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. /// Removes the specified <paramref name="roles"/> from this user in the guild.
/// </summary> /// </summary>
/// <param name="roles">The roles to be removed from the user.</param> /// <param name="roles">The roles to be removed from the user.</param>


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

@@ -12,7 +12,7 @@ namespace Discord
/// </summary> /// </summary>
/// <param name="value">The snowflake identifier to resolve.</param> /// <param name="value">The snowflake identifier to resolve.</param>
/// <returns> /// <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> /// </returns>
public static DateTimeOffset FromSnowflake(ulong value) public static DateTimeOffset FromSnowflake(ulong value)
=> DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL));


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

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


[JsonProperty("changes")] [JsonProperty("changes")]
public AuditLogChange[] Changes { get; set; } public AuditLogChange[] Changes { 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; } public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("referenced_message")] [JsonProperty("referenced_message")]
public Optional<Message> ReferencedMessage { get; set; } public Optional<Message> ReferencedMessage { get; set; }
[JsonProperty("stickers")]
public Optional<Sticker[]> Stickers { get; set; }
} }
} }

+ 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; }
}
}

+ 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; }
}
}

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

@@ -17,7 +17,7 @@ namespace Discord.Rest
return RestApplication.Create(client, model); 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) ulong id, RequestOptions options)
{ {
var model = await client.ApiClient.GetChannelAsync(id, options).ConfigureAwait(false); var model = await client.ApiClient.GetChannelAsync(id, options).ConfigureAwait(false);
@@ -45,13 +45,13 @@ namespace Discord.Rest
.Where(x => x.Type == ChannelType.Group) .Where(x => x.Type == ChannelType.Group)
.Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray(); .Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray();
} }
public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options)
{ {
var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false);
return models.Select(RestConnection.Create).ToImmutableArray(); return models.Select(RestConnection.Create).ToImmutableArray();
} }
public static async Task<RestInviteMetadata> GetInviteAsync(BaseDiscordClient client, public static async Task<RestInviteMetadata> GetInviteAsync(BaseDiscordClient client,
string inviteId, RequestOptions options) string inviteId, RequestOptions options)
{ {
@@ -60,7 +60,7 @@ namespace Discord.Rest
return RestInviteMetadata.Create(client, null, null, model); return RestInviteMetadata.Create(client, null, null, model);
return null; return null;
} }
public static async Task<RestGuild> GetGuildAsync(BaseDiscordClient client, public static async Task<RestGuild> GetGuildAsync(BaseDiscordClient client,
ulong id, bool withCounts, RequestOptions options) ulong id, bool withCounts, RequestOptions options)
{ {
@@ -85,7 +85,7 @@ namespace Discord.Rest
return RestGuildWidget.Create(model); return RestGuildWidget.Create(model);
return null; return null;
} }
public static IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(BaseDiscordClient client,
public static IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(BaseDiscordClient client,
ulong? fromGuildId, int? limit, RequestOptions options) ulong? fromGuildId, int? limit, RequestOptions options)
{ {
return new PagedAsyncEnumerable<RestUserGuild>( return new PagedAsyncEnumerable<RestUserGuild>(
@@ -136,7 +136,7 @@ namespace Discord.Rest
var model = await client.ApiClient.CreateGuildAsync(args, options).ConfigureAwait(false); var model = await client.ApiClient.CreateGuildAsync(args, options).ConfigureAwait(false);
return RestGuild.Create(client, model); return RestGuild.Create(client, model);
} }
public static async Task<RestUser> GetUserAsync(BaseDiscordClient client, public static async Task<RestUser> GetUserAsync(BaseDiscordClient client,
ulong id, RequestOptions options) ulong id, RequestOptions options)
{ {
@@ -201,5 +201,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);
} }
} }

+ 46
- 0
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -523,6 +523,43 @@ namespace Discord.API
var ids = new BucketIds(webhookId: webhookId); 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); 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> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null)
{ {
@@ -1243,6 +1280,15 @@ namespace Discord.API
} }


//Guild emoji //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) public async Task<Emoji> GetGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null)
{ {
Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(guildId, 0, nameof(guildId));


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

@@ -107,7 +107,19 @@ namespace Discord.Rest
=> ClientHelper.GetVoiceRegionAsync(this, id, options); => ClientHelper.GetVoiceRegionAsync(this, id, options);
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetWebhookAsync(this, id, options); => 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 //IDiscordClient
/// <inheritdoc /> /// <inheritdoc />
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) 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> /// </summary>
public struct ChannelInfo 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; Name = name;
Topic = topic; Topic = topic;
SlowModeInterval = rateLimit; SlowModeInterval = rateLimit;
IsNsfw = nsfw; IsNsfw = nsfw;
Bitrate = bitrate; Bitrate = bitrate;
ChannelType = type;
} }


/// <summary> /// <summary>
@@ -53,5 +54,12 @@ namespace Discord.Rest
/// <c>null</c> if this is not mentioned in this entry. /// <c>null</c> if this is not mentioned in this entry.
/// </returns> /// </returns>
public int? Bitrate { get; } 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 rateLimitPerUserModel = changes.FirstOrDefault(x => x.ChangedProperty == "rate_limit_per_user");
var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw"); var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw");
var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); 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), string oldName = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer),
newName = nameModel?.NewValue?.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); newNsfw = nsfwModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer);
int? oldBitrate = bitrateModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), int? oldBitrate = bitrateModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer),
newBitrate = bitrateModel?.NewValue?.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); 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) 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> /// <summary>
@@ -38,10 +44,10 @@ namespace Discord.Rest
/// </returns> /// </returns>
public ulong ChannelId { get; } public ulong ChannelId { get; }
/// <summary> /// <summary>
/// Gets the user of the message that was pinned.
/// Gets the user of the message that was pinned if available.
/// </summary> /// </summary>
/// <returns> /// <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> /// </returns>
public IUser Target { get; } 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) 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> /// <summary>
@@ -38,10 +44,10 @@ namespace Discord.Rest
/// </returns> /// </returns>
public ulong ChannelId { get; } public ulong ChannelId { get; }
/// <summary> /// <summary>
/// Gets the user of the message that was unpinned.
/// Gets the user of the message that was unpinned if available.
/// </summary> /// </summary>
/// <returns> /// <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> /// </returns>
public IUser Target { get; } 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) 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; IUser user = null;
if (userInfo != null) if (userInfo != null)
user = RestUser.Create(discord, userInfo); user = RestUser.Create(discord, userInfo);


+ 7
- 0
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -286,6 +286,13 @@ namespace Discord.Rest
return RestUserMessage.Create(client, channel, client.CurrentUser, model); 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, public static Task DeleteMessageAsync(IMessageChannel channel, ulong messageId, BaseDiscordClient client,
RequestOptions options) RequestOptions options)
=> MessageHelper.DeleteAsync(channel.Id, messageId, client, options); => MessageHelper.DeleteAsync(channel.Id, messageId, client, options);


+ 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) public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); => 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 /> /// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => 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) public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); => 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 /> /// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) 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) public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); => 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 /> /// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);


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

@@ -496,6 +496,11 @@ namespace Discord.Rest
} }


//Emotes //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) 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); var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options).ConfigureAwait(false);


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

@@ -828,6 +828,9 @@ namespace Discord.Rest


//Emotes //Emotes
/// <inheritdoc /> /// <inheritdoc />
public Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null)
=> GuildHelper.GetEmotesAsync(this, Discord, options);
/// <inheritdoc />
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetEmoteAsync(this, Discord, id, options); => GuildHelper.GetEmoteAsync(this, Discord, id, options);
/// <inheritdoc /> /// <inheritdoc />


+ 62
- 0
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); 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) public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
=> DeleteAsync(msg.Channel.Id, msg.Id, client, options); => DeleteAsync(msg.Channel.Id, msg.Id, client, options);


@@ -89,21 +131,41 @@ namespace Discord.Rest
await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false);
} }


public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options)
{
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) public static async Task AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options)
{ {
await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false);
} }


public static async Task RemoveReactionAsync(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) 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); 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) public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
{ {
await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); 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) 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); 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>(); public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>();
/// <inheritdoc /> /// <inheritdoc />
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();
/// <inheritdoc />
public virtual IReadOnlyCollection<Sticker> Stickers => ImmutableArray.Create<Sticker>();


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


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


+ 17
- 0
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<ITag> _tags = ImmutableArray.Create<ITag>();
private ImmutableArray<ulong> _roleMentionIds = ImmutableArray.Create<ulong>(); private ImmutableArray<ulong> _roleMentionIds = ImmutableArray.Create<ulong>();
private ImmutableArray<RestUser> _userMentions = ImmutableArray.Create<RestUser>(); private ImmutableArray<RestUser> _userMentions = ImmutableArray.Create<RestUser>();
private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>();


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


internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source)
@@ -132,6 +135,20 @@ namespace Discord.Rest
IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable()); IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable());
_referencedMessage = RestUserMessage.Create(Discord, Channel, refMsgAuthor, refMsg); _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 /> /// <inheritdoc />


+ 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})";
}
}

+ 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) public Task KickAsync(string reason = null, RequestOptions options = null)
=> UserHelper.KickAsync(this, Discord, reason, options); => UserHelper.KickAsync(this, Discord, reason, options);
/// <inheritdoc /> /// <inheritdoc />
public Task AddRoleAsync(ulong roleId, RequestOptions options = null)
=> AddRolesAsync(new[] { roleId }, options);
/// <inheritdoc />
public Task AddRoleAsync(IRole role, RequestOptions options = null) 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 /> /// <inheritdoc />
public Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) 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 /> /// <inheritdoc />
public Task RemoveRoleAsync(IRole role, RequestOptions options = null) 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 /> /// <inheritdoc />
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null)
=> UserHelper.RemoveRolesAsync(this, Discord, roles, options);
=> RemoveRolesAsync(roles.Select(x => x.Id));


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


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

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


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

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

/// <inheritdoc /> /// <inheritdoc />
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."); throw new NotSupportedException("Roles are not supported on webhook users.");

/// <inheritdoc /> /// <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."); throw new NotSupportedException("Roles are not supported on webhook users.");

/// <inheritdoc /> /// <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."); throw new NotSupportedException("Roles are not supported on webhook users.");


//IVoiceState //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)); 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 IGuild Guild { get; private set; }
internal ITextChannel Channel { get; private set; } internal ITextChannel Channel { get; private set; }


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


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


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


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

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

@@ -315,22 +315,6 @@ namespace Discord.WebSocket
} }
internal readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>(); internal readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>();


//Invites
internal readonly AsyncEvent<Func<SocketGuildInvite, Task>> _inviteCreatedEvent = new AsyncEvent<Func<SocketGuildInvite, Task>>();
/// <summary> Fired when a invite is created. </summary>
public event Func<SocketGuildInvite, Task> InviteCreated
{
add { _inviteCreatedEvent.Add(value); }
remove { _inviteCreatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<SocketGuildInvite, string>, Task>> _inviteDeletedEvent = new AsyncEvent<Func<Cacheable<SocketGuildInvite, string>, Task>>();
/// <summary> Fired when a invite is deleted. </summary>
public event Func<Cacheable<SocketGuildInvite, string>, Task> InviteDeleted
{
add { _inviteDeletedEvent.Add(value); }
remove { _inviteDeletedEvent.Remove(value); }
}

//Users //Users
/// <summary> Fired when a user joins a guild. </summary> /// <summary> Fired when a user joins a guild. </summary>
public event Func<SocketGuildUser, Task> UserJoined { public event Func<SocketGuildUser, Task> UserJoined {


+ 10
- 0
src/Discord.Net.WebSocket/BaseSocketClient.cs View File

@@ -209,6 +209,12 @@ namespace Discord.WebSocket
/// <param name="name">The name of the game.</param> /// <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="streamUrl">If streaming, the URL of the stream. Must be a valid Twitch URL.</param>
/// <param name="type">The type of the game.</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> /// <returns>
/// A task that represents the asynchronous set operation. /// A task that represents the asynchronous set operation.
/// </returns> /// </returns>
@@ -222,6 +228,10 @@ namespace Discord.WebSocket
/// Discord will only accept setting of name and the type of activity. /// Discord will only accept setting of name and the type of activity.
/// </note> /// </note>
/// <note type="warning"> /// <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 /// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC
/// clients only. /// clients only.
/// </note> /// </note>


+ 9
- 56
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

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


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


await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false); await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false);
} }
@@ -1741,61 +1749,6 @@ namespace Discord.WebSocket


} }
break; break;
case "INVITE_CREATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false);
var data = (payload as JToken).ToObject<InviteCreatedEvent>(_serializer);
if(data.GuildID.HasValue)
{
var guild = State.GetGuild(data.GuildID.Value);
if (guild != null)
{
if (!guild.IsSynced)
{
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false);
return;
}

var channel = guild.GetChannel(data.ChannelID);

if (channel != null)
{
var invite = new SocketGuildInvite(this, guild, channel, data.InviteCode, data);
guild.AddSocketInvite(invite);
await TimedInvokeAsync(_inviteCreatedEvent, nameof(InviteCreated), invite).ConfigureAwait(false);
}
}
}
}
break;
case "INVITE_DELETE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false);
var data = (payload as JToken).ToObject<InviteDeletedEvent>(_serializer);
if(data.GuildID.IsSpecified)
{
var guild = State.GetGuild(data.GuildID.Value);
if (guild != null)
{
if (!guild.IsSynced)
{
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false);
return;
}

var channel = guild.GetChannel(data.ChannelID);

if (channel != null)
{
var invite = guild.RemoveSocketInvite(data.Code);
var cache = new Cacheable<SocketGuildInvite, string>(null, data.Code, invite != null, async () => await guild.GetSocketInviteAsync(data.Code));
await TimedInvokeAsync(_inviteDeletedEvent, nameof(InviteDeleted), cache).ConfigureAwait(false);
}
}
}

}
break;


//Invites //Invites
case "INVITE_CREATE": case "INVITE_CREATE":


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

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


/// <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 /> /// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => ChannelHelper.TriggerTypingAsync(this, Discord, options);


+ 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) public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); => 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 /> /// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null) public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); => 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) public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); => 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 /> /// <inheritdoc />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);


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

@@ -39,7 +39,6 @@ namespace Discord.WebSocket
private ImmutableArray<GuildEmote> _emotes; private ImmutableArray<GuildEmote> _emotes;
private ImmutableArray<string> _features; private ImmutableArray<string> _features;
private AudioClient _audioClient; private AudioClient _audioClient;
private InviteCache _invites;
#pragma warning restore IDISP002, IDISP006 #pragma warning restore IDISP002, IDISP006


/// <inheritdoc /> /// <inheritdoc />
@@ -361,7 +360,6 @@ namespace Discord.WebSocket
_audioLock = new SemaphoreSlim(1, 1); _audioLock = new SemaphoreSlim(1, 1);
_emotes = ImmutableArray.Create<GuildEmote>(); _emotes = ImmutableArray.Create<GuildEmote>();
_features = ImmutableArray.Create<string>(); _features = ImmutableArray.Create<string>();
_invites = new InviteCache(client);
} }
internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model) internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model)
{ {
@@ -617,22 +615,6 @@ namespace Discord.WebSocket
public Task RemoveBanAsync(ulong userId, RequestOptions options = null) public Task RemoveBanAsync(ulong userId, RequestOptions options = null)
=> GuildHelper.RemoveBanAsync(this, Discord, userId, options); => GuildHelper.RemoveBanAsync(this, Discord, userId, options);


//Invites
internal void AddSocketInvite(SocketGuildInvite invite)
=> _invites.Add(invite);
internal SocketGuildInvite RemoveSocketInvite(string code)
=> _invites.Remove(code);
internal async Task<SocketGuildInvite> GetSocketInviteAsync(string code)
{
var invites = await this.GetInvitesAsync();
RestInviteMetadata restInvite = invites.First(x => x.Code == code);
if (restInvite == null)
return null;
var invite = new SocketGuildInvite(Discord, this, this.GetChannel(restInvite.ChannelId), code, restInvite);
return invite;
}

//Channels //Channels
/// <summary> /// <summary>
/// Gets a channel in this guild. /// Gets a channel in this guild.
@@ -1026,6 +1008,9 @@ namespace Discord.WebSocket


//Emotes //Emotes
/// <inheritdoc /> /// <inheritdoc />
public Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null)
=> GuildHelper.GetEmotesAsync(this, Discord, options);
/// <inheritdoc />
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetEmoteAsync(this, Discord, id, options); => GuildHelper.GetEmoteAsync(this, Discord, id, options);
/// <inheritdoc /> /// <inheritdoc />


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

@@ -99,6 +99,8 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();
/// <inheritdoc /> /// <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) }); 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 /> /// <inheritdoc />
@@ -194,6 +196,8 @@ namespace Discord.WebSocket
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray();
/// <inheritdoc /> /// <inheritdoc />
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray();
/// <inheritdoc />
IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers;


internal void AddReaction(SocketReaction reaction) internal void AddReaction(SocketReaction reaction)
{ {


+ 17
- 0
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<ITag> _tags = ImmutableArray.Create<ITag>();
private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>(); private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>();
private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>(); private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>();
private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>();


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


internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) 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); refMsgAuthor = new SocketUnknownUser(Discord, id: 0);
_referencedMessage = SocketUserMessage.Create(Discord, state, refMsgAuthor, Channel, refMsg); _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 /> /// <inheritdoc />


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

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


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


+ 27
- 7
src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs View File

@@ -31,7 +31,7 @@ namespace Discord.WebSocket
public override bool IsWebhook => true; public override bool IsWebhook => true;
/// <inheritdoc /> /// <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, null); } set { } }
internal override SocketGlobalUser GlobalUser =>
internal override SocketGlobalUser GlobalUser =>
throw new NotSupportedException(); throw new NotSupportedException();


internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) 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); ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue);
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Webhook users cannot be kicked.</exception> /// <exception cref="NotSupportedException">Webhook users cannot be kicked.</exception>
Task IGuildUser.KickAsync(string reason, RequestOptions options) =>
Task IGuildUser.KickAsync(string reason, RequestOptions options) =>
throw new NotSupportedException("Webhook users cannot be kicked."); throw new NotSupportedException("Webhook users cannot be kicked.");


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


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception> /// <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."); throw new NotSupportedException("Roles are not supported on webhook users.");


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception> /// <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."); throw new NotSupportedException("Roles are not supported on webhook users.");


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception> /// <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."); throw new NotSupportedException("Roles are not supported on webhook users.");


/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception> /// <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."); throw new NotSupportedException("Roles are not supported on webhook users.");


//IVoiceState //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) string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null)
=> WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options); => 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> /// <summary> Sends a message to the channel for this webhook with an attachment. </summary>
/// <returns> Returns the ID of the created message. </returns> /// <returns> Returns the ID of the created message. </returns>
public Task<ulong> SendFileAsync(string filePath, string text, bool isTTS = false, 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; }
}
}

+ 4
- 2
src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Webhook; using Model = Discord.API.Webhook;
@@ -11,9 +11,9 @@ namespace Discord.Webhook
private DiscordWebhookClient _client; private DiscordWebhookClient _client;


public ulong Id { get; } public ulong Id { get; }
public ulong ChannelId { get; }
public string Token { get; } public string Token { get; }


public ulong ChannelId { get; private set; }
public string Name { get; private set; } public string Name { get; private set; }
public string AvatarId { get; private set; } public string AvatarId { get; private set; }
public ulong? GuildId { get; private set; } public ulong? GuildId { get; private set; }
@@ -36,6 +36,8 @@ namespace Discord.Webhook


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


+ 53
- 1
src/Discord.Net.Webhook/WebhookClientHelper.cs View File

@@ -36,7 +36,59 @@ namespace Discord.Webhook
var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false);
return model.Id; return model.Id;
} }
public static async Task<ulong> SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS,
public static async Task ModifyMessageAsync(DiscordWebhookClient client, ulong messageId,
Action<WebhookMessageProperties> func, RequestOptions options)
{
var args = new WebhookMessageProperties();
func(args);

if (args.AllowedMentions.IsSpecified)
{
var 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?.AllowedTypes != null)
{
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 ModifyWebhookMessageParams
{
Content = args.Content.IsSpecified ? args.Content.Value : Optional.Create<string>(),
Embeds =
args.Embeds.IsSpecified
? args.Embeds.Value.Select(embed => embed.ToModel()).ToArray()
: Optional.Create<API.Embed[]>(),
AllowedMentions = args.AllowedMentions.IsSpecified
? args.AllowedMentions.Value.ToModel()
: Optional.Create<API.AllowedMentions>()
};

await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options)
.ConfigureAwait(false);
}
public static async Task DeleteMessageAsync(DiscordWebhookClient client, ulong messageId, RequestOptions options)
{
await client.ApiClient.DeleteWebhookMessageAsync(client.Webhook.Id, messageId, options).ConfigureAwait(false);
}
public static async Task<ulong> SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS,
IEnumerable<Embed> embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler) IEnumerable<Embed> embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler)
{ {
string filename = Path.GetFileName(filePath); string filename = Path.GetFileName(filePath);


+ 16
- 16
src/Discord.Net/Discord.Net.nuspec View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Discord.Net</id> <id>Discord.Net</id>
<version>2.3.1-dev$suffix$</version>
<version>2.4.0$suffix$</version>
<title>Discord.Net</title> <title>Discord.Net</title>
<authors>Discord.Net Contributors</authors> <authors>Discord.Net Contributors</authors>
<owners>foxbot</owners> <owners>foxbot</owners>
@@ -14,25 +14,25 @@
<iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl> <iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl>
<dependencies> <dependencies>
<group targetFramework="net461"> <group targetFramework="net461">
<dependency id="Discord.Net.Core" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Core" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Rest" version="2.4.0$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Commands" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.4.0$suffix$" />
</group> </group>
<group targetFramework="netstandard2.0"> <group targetFramework="netstandard2.0">
<dependency id="Discord.Net.Core" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Core" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Rest" version="2.4.0$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Commands" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.4.0$suffix$" />
</group> </group>
<group targetFramework="netstandard2.1"> <group targetFramework="netstandard2.1">
<dependency id="Discord.Net.Core" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.3.1-dev$suffix$" />
<dependency id="Discord.Net.Core" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Rest" version="2.4.0$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Commands" version="2.4.0$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.4.0$suffix$" />
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>


Loading…
Cancel
Save