| @@ -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>(); | ||||
| @@ -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> | ||||
| @@ -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> | ||||
| @@ -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. | ||||
| @@ -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> | ||||
| @@ -0,0 +1,67 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a discord sticker. | |||||
| /// </summary> | |||||
| public interface ISticker | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the ID of this sticker. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A snowflake ID associated with this sticker. | |||||
| /// </returns> | |||||
| ulong Id { get; } | |||||
| /// <summary> | |||||
| /// Gets the ID of the pack of this sticker. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A snowflake ID associated with the pack of this sticker. | |||||
| /// </returns> | |||||
| ulong PackId { get; } | |||||
| /// <summary> | |||||
| /// Gets the name of this sticker. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see langword="string"/> with the name of this sticker. | |||||
| /// </returns> | |||||
| string Name { get; } | |||||
| /// <summary> | |||||
| /// Gets the description of this sticker. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see langword="string"/> with the description of this sticker. | |||||
| /// </returns> | |||||
| string Description { get; } | |||||
| /// <summary> | |||||
| /// Gets the list of tags of this sticker. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A read-only list with the tags of this sticker. | |||||
| /// </returns> | |||||
| IReadOnlyCollection<string> Tags { get; } | |||||
| /// <summary> | |||||
| /// Gets the asset hash of this sticker. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see langword="string"/> with the asset hash of this sticker. | |||||
| /// </returns> | |||||
| string Asset { get; } | |||||
| /// <summary> | |||||
| /// Gets the preview asset hash of this sticker. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see langword="string"/> with the preview asset hash of this sticker. | |||||
| /// </returns> | |||||
| string PreviewAsset { get; } | |||||
| /// <summary> | |||||
| /// Gets the format type of this sticker. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="StickerFormatType"/> with the format type of this sticker. | |||||
| /// </returns> | |||||
| StickerFormatType FormatType { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,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, | |||||
| } | |||||
| } | |||||
| @@ -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> | ||||
| @@ -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)); | ||||
| @@ -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; } | ||||
| @@ -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; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -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); | |||||
| } | } | ||||
| } | } | ||||
| @@ -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)); | ||||
| @@ -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) | ||||
| @@ -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; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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); | ||||
| } | } | ||||
| @@ -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; } | ||||
| } | } | ||||
| @@ -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; } | ||||
| } | } | ||||
| @@ -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); | ||||
| @@ -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); | ||||
| @@ -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); | ||||
| @@ -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) | ||||
| @@ -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); | ||||
| @@ -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); | ||||
| @@ -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 /> | ||||
| @@ -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); | ||||
| @@ -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 }); | ||||
| @@ -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 /> | ||||
| @@ -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})"; | |||||
| } | |||||
| } | |||||
| @@ -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> | ||||
| @@ -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 | ||||
| @@ -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); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -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) | ||||
| @@ -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; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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 { | ||||
| @@ -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> | ||||
| @@ -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": | ||||
| @@ -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); | ||||
| @@ -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); | ||||
| @@ -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); | ||||
| @@ -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 /> | ||||
| @@ -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) | ||||
| { | { | ||||
| @@ -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 /> | ||||
| @@ -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) | ||||
| @@ -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 | ||||
| @@ -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, | ||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -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) | ||||
| @@ -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); | ||||
| @@ -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> | ||||