diff --git a/src/Discord.Net.Core/Entities/Emotes/ApplicationEmoteProperties.cs b/src/Discord.Net.Core/Entities/Emotes/ApplicationEmoteProperties.cs new file mode 100644 index 0000000000..457c338327 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Emotes/ApplicationEmoteProperties.cs @@ -0,0 +1,12 @@ +namespace Discord; + +/// +/// Represents the properties for an application emote. +/// +public class ApplicationEmoteProperties +{ + /// + /// Gets or sets the name of the emote. + /// + public string Name { get; set; } +} diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index ed5fd682b7..768d04b136 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -12,8 +12,10 @@ public class Emote : IEmote, ISnowflakeEntity { /// public string Name { get; } + /// public ulong Id { get; } + /// /// Gets whether this emote is animated. /// @@ -21,8 +23,10 @@ public class Emote : IEmote, ISnowflakeEntity /// A boolean that determines whether or not this emote is an animated one. /// public bool Animated { get; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// /// Gets the image URL of this emote. /// @@ -31,6 +35,11 @@ public class Emote : IEmote, ISnowflakeEntity /// public string Url => CDN.GetEmojiUrl(Id, Animated); + /// + /// Gets the user who created this emote. if not available. + /// + public IUser User { get; private set; } + /// /// Creates a new instance of . /// @@ -41,6 +50,14 @@ public Emote(ulong id, string name, bool animated = false) Animated = animated; } + internal Emote(ulong id, string name, bool animated = false, IUser user = null) + { + Id = id; + Name = name; + Animated = animated; + User = user; + } + /// /// Determines whether the specified emote is equal to the current emote. /// diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index 90562840da..43e77020c1 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -356,5 +356,30 @@ IAsyncEnumerable> GetEntitlementsAsync(int? li /// The id of the entitlement. /// The options to be used when sending the request. Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null); + + /// + /// Gets an emote for the current application. + /// + public Task GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null); + + /// + /// Gets all emotes for the current application. + /// + public Task> GetApplicationEmotesAsync(RequestOptions options = null); + + /// + /// Modifies an emote for the current application. + /// + public Task ModifyApplicationEmoteAsync(ulong emoteId, Action args, RequestOptions options = null); + + /// + /// Creates an emote for the current application. + /// + public Task CreateApplicationEmoteAsync(string name, Image image, RequestOptions options = null); + + /// + /// Deletes an emote for the current application. + /// + public Task DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/API/Common/Emoji.cs b/src/Discord.Net.Rest/API/Common/Emoji.cs index d35b42ad49..00f666f8df 100644 --- a/src/Discord.Net.Rest/API/Common/Emoji.cs +++ b/src/Discord.Net.Rest/API/Common/Emoji.cs @@ -11,16 +11,16 @@ internal class Emoji public string Name { get; set; } [JsonProperty("animated")] - public bool? Animated { get; set; } + public Optional Animated { get; set; } [JsonProperty("roles")] - public ulong[] Roles { get; set; } + public Optional Roles { get; set; } [JsonProperty("require_colons")] - public bool RequireColons { get; set; } + public Optional RequireColons { get; set; } [JsonProperty("managed")] - public bool Managed { get; set; } + public Optional Managed { get; set; } [JsonProperty("user")] public Optional User { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/ListApplicationEmojisResponse.cs b/src/Discord.Net.Rest/API/Common/ListApplicationEmojisResponse.cs new file mode 100644 index 0000000000..1d59f13216 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ListApplicationEmojisResponse.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class ListApplicationEmojisResponse +{ + [JsonProperty("items")] + public Emoji[] Items { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateApplicationEmoteParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationEmoteParams.cs new file mode 100644 index 0000000000..45c0d775ba --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationEmoteParams.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest; + +internal class CreateApplicationEmoteParams +{ + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("image")] + public Image Image { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyApplicationEmoteParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyApplicationEmoteParams.cs new file mode 100644 index 0000000000..b8daffbf95 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyApplicationEmoteParams.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest; + +internal class ModifyApplicationEmoteParams +{ + [JsonProperty("name")] + public Optional Name { get; set; } +} diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 372dea35a2..8b95d8b321 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -299,6 +299,22 @@ IAsyncEnumerable> IDiscordClient.GetEntitlemen /// Task IDiscordClient.ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options) => Task.CompletedTask; + + /// + Task IDiscordClient.GetApplicationEmoteAsync(ulong emoteId, RequestOptions options) => Task.FromResult(null); + + /// + Task> IDiscordClient.GetApplicationEmotesAsync(RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); + + /// + Task IDiscordClient.ModifyApplicationEmoteAsync(ulong emoteId, Action args, RequestOptions options) => Task.FromResult(null); + + /// + Task IDiscordClient.CreateApplicationEmoteAsync(string name, Image image, RequestOptions options) => Task.FromResult(null); + + /// + Task IDiscordClient.DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options) => Task.CompletedTask; + #endregion } } diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index b660697a27..6f7654da8d 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -450,5 +450,48 @@ public static Task ConsumeEntitlementAsync(BaseDiscordClient client, ulong entit => client.ApiClient.ConsumeEntitlementAsync(entitlementId, options); #endregion + + #region Application Emojis + + public static async Task> GetApplicationEmojisAsync(BaseDiscordClient client, RequestOptions options = null) + { + var model = await client.ApiClient.GetApplicationEmotesAsync(options).ConfigureAwait(false); + return model.Items.Select(x => x.ToEmote(client)).ToImmutableArray(); + } + + public static async Task GetApplicationEmojiAsync(BaseDiscordClient client, ulong emojiId, RequestOptions options = null) + { + var model = await client.ApiClient.GetApplicationEmoteAsync(emojiId, options).ConfigureAwait(false); + return model.ToEmote(client); + } + + public static async Task CreateApplicationEmojiAsync(BaseDiscordClient client, string name, Image image, RequestOptions options = null) + { + var model = await client.ApiClient.CreateApplicationEmoteAsync(new CreateApplicationEmoteParams + { + Name = name, + Image = image.ToModel() + }, options).ConfigureAwait(false); + + return model.ToEmote(client); + } + + public static async Task ModifyApplicationEmojiAsync(BaseDiscordClient client, ulong emojiId, Action func, RequestOptions options = null) + { + var args = new ApplicationEmoteProperties(); + func(args); + + var model = await client.ApiClient.ModifyApplicationEmoteAsync(emojiId, new ModifyApplicationEmoteParams + { + Name = args.Name, + }, options).ConfigureAwait(false); + + return model.ToEmote(client); + } + + public static Task DeleteApplicationEmojiAsync(BaseDiscordClient client, ulong emojiId, RequestOptions options = null) + => client.ApiClient.DeleteApplicationEmoteAsync(emojiId, options); + + #endregion } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index e73a8e70f3..beaf015550 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -2093,7 +2093,7 @@ public Task> ModifyGuildRolesAsync(ulong guildId, IEnu } #endregion - #region Guild emoji + #region Guild Emoji public Task> GetGuildEmotesAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -2845,5 +2845,56 @@ public Task ExpirePollAsync(ulong channelId, ulong messageId, RequestOp => SendAsync("POST", () => $"channels/{channelId}/polls/{messageId}/expire", new BucketIds(channelId: channelId), options: options); #endregion + + #region App Emojis + + public Task CreateApplicationEmoteAsync(CreateApplicationEmoteParams args, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNull(args.Image.Stream, nameof(args.Image)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(); + return SendJsonAsync("POST", () => $"applications/{CurrentApplicationId}/emojis", args, ids, options: options); + } + + public Task ModifyApplicationEmoteAsync(ulong emoteId, ModifyApplicationEmoteParams args, RequestOptions options = null) + { + Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); + Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(); + return SendJsonAsync("PATCH", () => $"applications/{CurrentApplicationId}/emojis/{emoteId}", args, ids, options: options); + } + + public Task DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options = null) + { + Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(); + return SendAsync("DELETE", () => $"applications/{CurrentApplicationId}/emojis/{emoteId}", ids, options: options); + } + + public Task GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null) + { + Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(); + return SendAsync("GET", () => $"applications/{CurrentApplicationId}/emojis/{emoteId}", ids, options: options); + } + + public Task GetApplicationEmotesAsync(RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(); + return SendAsync("GET", () => $"applications/{CurrentApplicationId}/emojis", ids, options: options); + } + + #endregion } } diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 0d5ae05843..404e84af1c 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -300,6 +300,26 @@ public Task> GetSKUsAsync(RequestOptions options = null public Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null) => ClientHelper.ConsumeEntitlementAsync(this, entitlementId, options); + /// + public Task GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null) + => ClientHelper.GetApplicationEmojiAsync(this, emoteId, options); + + /// + public Task> GetApplicationEmotesAsync(RequestOptions options = null) + => ClientHelper.GetApplicationEmojisAsync(this, options); + + /// + public Task ModifyApplicationEmoteAsync(ulong emoteId, Action args, RequestOptions options = null) + => ClientHelper.ModifyApplicationEmojiAsync(this, emoteId, args, options); + + /// + public Task CreateApplicationEmoteAsync(string name, Image image, RequestOptions options = null) + => ClientHelper.CreateApplicationEmojiAsync(this, name, image, options); + + /// + public Task DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options = null) + => ClientHelper.DeleteApplicationEmojiAsync(this, emoteId, options); + #endregion #region IDiscordClient diff --git a/src/Discord.Net.Rest/Entities/Guilds/Onboarding/RestGuildOnboardingPromptOption.cs b/src/Discord.Net.Rest/Entities/Guilds/Onboarding/RestGuildOnboardingPromptOption.cs index 513c836cd4..768e685308 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/Onboarding/RestGuildOnboardingPromptOption.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/Onboarding/RestGuildOnboardingPromptOption.cs @@ -36,7 +36,7 @@ internal RestGuildOnboardingPromptOption(BaseDiscordClient discord, ulong id, Mo if (model.Emoji.Id.HasValue) { - Emoji = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated ?? false); + Emoji = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated.GetValueOrDefault(false)); } else if (!string.IsNullOrWhiteSpace(model.Emoji.Name)) { diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index 3e7ec81bbb..e6a510a24c 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -13,13 +14,21 @@ public static IEmote ToIEmote(this API.Emoji model) return new Emoji(model.Name); } + public static Emote ToEmote(this API.Emoji model, BaseDiscordClient discord = null) + => new(model.Id.GetValueOrDefault(), + model.Name, + model.Animated.GetValueOrDefault(false), + model.User.IsSpecified ? + RestUser.Create(discord, model.User.Value) + : null); + public static GuildEmote ToEntity(this API.Emoji model) => new GuildEmote(model.Id.Value, model.Name, - model.Animated.GetValueOrDefault(), - model.Managed, - model.RequireColons, - ImmutableArray.Create(model.Roles), + model.Animated.GetValueOrDefault(false), + model.Managed.GetValueOrDefault(false), + model.RequireColons.GetValueOrDefault(false), + model.Roles.GetValueOrDefault([]).ToImmutableArray(), model.User.IsSpecified ? model.User.Value.Id : null, model.IsAvailable.ToNullable()); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 06179e10ea..d4754d7dd2 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -451,6 +451,26 @@ public Task> GetSKUsAsync(RequestOptions options = null public Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null) => ClientHelper.ConsumeEntitlementAsync(this, entitlementId, options); + /// + public Task GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null) + => ClientHelper.GetApplicationEmojiAsync(this, emoteId, options); + + /// + public Task> GetApplicationEmotesAsync(RequestOptions options = null) + => ClientHelper.GetApplicationEmojisAsync(this, options); + + /// + public Task ModifyApplicationEmoteAsync(ulong emoteId, Action args, RequestOptions options = null) + => ClientHelper.ModifyApplicationEmojiAsync(this, emoteId, args, options); + + /// + public Task CreateApplicationEmoteAsync(string name, Image image, RequestOptions options = null) + => ClientHelper.CreateApplicationEmojiAsync(this, name, image, options); + + /// + public Task DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options = null) + => ClientHelper.DeleteApplicationEmojiAsync(this, emoteId, options); + /// /// Gets entitlements from cache. /// diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/Onboarding/SocketGuildOnboardingPromptOption.cs b/src/Discord.Net.WebSocket/Entities/Guilds/Onboarding/SocketGuildOnboardingPromptOption.cs index 4e96a9f6d9..992d08696c 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/Onboarding/SocketGuildOnboardingPromptOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/Onboarding/SocketGuildOnboardingPromptOption.cs @@ -47,7 +47,7 @@ internal SocketGuildOnboardingPromptOption(DiscordSocketClient discord, ulong id if (model.Emoji.Id.HasValue) { - Emoji = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated ?? false); + Emoji = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated.GetValueOrDefault(false)); } else if (!string.IsNullOrWhiteSpace(model.Emoji.Name)) {