Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Application Emojis :wires: #2963

Merged
merged 1 commit into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Discord.Net.Core/Entities/Emotes/ApplicationEmoteProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Discord;

/// <summary>
/// Represents the properties for an application emote.
/// </summary>
public class ApplicationEmoteProperties
{
/// <summary>
/// Gets or sets the name of the emote.
/// </summary>
public string Name { get; set; }
}
17 changes: 17 additions & 0 deletions src/Discord.Net.Core/Entities/Emotes/Emote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@ public class Emote : IEmote, ISnowflakeEntity
{
/// <inheritdoc />
public string Name { get; }

/// <inheritdoc />
public ulong Id { get; }

/// <summary>
/// Gets whether this emote is animated.
/// </summary>
/// <returns>
/// A boolean that determines whether or not this emote is an animated one.
/// </returns>
public bool Animated { get; }

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

/// <summary>
/// Gets the image URL of this emote.
/// </summary>
Expand All @@ -31,6 +35,11 @@ public class Emote : IEmote, ISnowflakeEntity
/// </returns>
public string Url => CDN.GetEmojiUrl(Id, Animated);

/// <summary>
/// Gets the user who created this emote. <see langword="null" /> if not available.
/// </summary>
public IUser User { get; private set; }

/// <summary>
/// Creates a new instance of <see cref="Emote" />.
/// </summary>
Expand All @@ -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;
}

/// <summary>
/// Determines whether the specified emote is equal to the current emote.
/// </summary>
Expand Down
25 changes: 25 additions & 0 deletions src/Discord.Net.Core/IDiscordClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,5 +356,30 @@ IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> GetEntitlementsAsync(int? li
/// <param name="entitlementId">The id of the entitlement.</param>
/// <param name="options">The options to be used when sending the request.</param>
Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null);

/// <summary>
/// Gets an emote for the current application.
/// </summary>
public Task<Emote> GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null);

/// <summary>
/// Gets all emotes for the current application.
/// </summary>
public Task<IReadOnlyCollection<Emote>> GetApplicationEmotesAsync(RequestOptions options = null);

/// <summary>
/// Modifies an emote for the current application.
/// </summary>
public Task<Emote> ModifyApplicationEmoteAsync(ulong emoteId, Action<ApplicationEmoteProperties> args, RequestOptions options = null);

/// <summary>
/// Creates an emote for the current application.
/// </summary>
public Task<Emote> CreateApplicationEmoteAsync(string name, Image image, RequestOptions options = null);

/// <summary>
/// Deletes an emote for the current application.
/// </summary>
public Task DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options = null);
}
}
8 changes: 4 additions & 4 deletions src/Discord.Net.Rest/API/Common/Emoji.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ internal class Emoji
public string Name { get; set; }

[JsonProperty("animated")]
public bool? Animated { get; set; }
public Optional<bool> Animated { get; set; }

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

[JsonProperty("require_colons")]
public bool RequireColons { get; set; }
public Optional<bool> RequireColons { get; set; }

[JsonProperty("managed")]
public bool Managed { get; set; }
public Optional<bool> Managed { get; set; }

[JsonProperty("user")]
public Optional<User> User { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Newtonsoft.Json;

namespace Discord.API;

internal class ListApplicationEmojisResponse
{
[JsonProperty("items")]
public Emoji[] Items { get; set; }
}
12 changes: 12 additions & 0 deletions src/Discord.Net.Rest/API/Rest/CreateApplicationEmoteParams.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
9 changes: 9 additions & 0 deletions src/Discord.Net.Rest/API/Rest/ModifyApplicationEmoteParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Newtonsoft.Json;

namespace Discord.API.Rest;

internal class ModifyApplicationEmoteParams
{
[JsonProperty("name")]
public Optional<string> Name { get; set; }
}
16 changes: 16 additions & 0 deletions src/Discord.Net.Rest/BaseDiscordClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,22 @@ IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> IDiscordClient.GetEntitlemen
/// </summary>
Task IDiscordClient.ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options) => Task.CompletedTask;


/// <inheritdoc />
Task<Emote> IDiscordClient.GetApplicationEmoteAsync(ulong emoteId, RequestOptions options) => Task.FromResult<Emote>(null);

/// <inheritdoc />
Task<IReadOnlyCollection<Emote>> IDiscordClient.GetApplicationEmotesAsync(RequestOptions options) => Task.FromResult<IReadOnlyCollection<Emote>>(ImmutableArray.Create<Emote>());

/// <inheritdoc />
Task<Emote> IDiscordClient.ModifyApplicationEmoteAsync(ulong emoteId, Action<ApplicationEmoteProperties> args, RequestOptions options) => Task.FromResult<Emote>(null);

/// <inheritdoc />
Task<Emote> IDiscordClient.CreateApplicationEmoteAsync(string name, Image image, RequestOptions options) => Task.FromResult<Emote>(null);

/// <inheritdoc />
Task IDiscordClient.DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options) => Task.CompletedTask;

#endregion
}
}
43 changes: 43 additions & 0 deletions src/Discord.Net.Rest/ClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IReadOnlyCollection<Emote>> 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<Emote> 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<Emote> 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<Emote> ModifyApplicationEmojiAsync(BaseDiscordClient client, ulong emojiId, Action<ApplicationEmoteProperties> 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
}
}
53 changes: 52 additions & 1 deletion src/Discord.Net.Rest/DiscordRestApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2093,7 +2093,7 @@ public Task<IReadOnlyCollection<Role>> ModifyGuildRolesAsync(ulong guildId, IEnu
}
#endregion

#region Guild emoji
#region Guild Emoji
public Task<IReadOnlyCollection<Emoji>> GetGuildEmotesAsync(ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Expand Down Expand Up @@ -2845,5 +2845,56 @@ public Task<Message> ExpirePollAsync(ulong channelId, ulong messageId, RequestOp
=> SendAsync<Message>("POST", () => $"channels/{channelId}/polls/{messageId}/expire", new BucketIds(channelId: channelId), options: options);

#endregion

#region App Emojis

public Task<Emoji> 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<Emoji>("POST", () => $"applications/{CurrentApplicationId}/emojis", args, ids, options: options);
}

public Task<Emoji> 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<Emoji>("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<Emoji> GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null)
{
Preconditions.NotEqual(emoteId, 0, nameof(emoteId));
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds();
return SendAsync<Emoji>("GET", () => $"applications/{CurrentApplicationId}/emojis/{emoteId}", ids, options: options);
}

public Task<ListApplicationEmojisResponse> GetApplicationEmotesAsync(RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds();
return SendAsync<ListApplicationEmojisResponse>("GET", () => $"applications/{CurrentApplicationId}/emojis", ids, options: options);
}

#endregion
}
}
20 changes: 20 additions & 0 deletions src/Discord.Net.Rest/DiscordRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,26 @@ public Task<IReadOnlyCollection<SKU>> GetSKUsAsync(RequestOptions options = null
public Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null)
=> ClientHelper.ConsumeEntitlementAsync(this, entitlementId, options);

/// <inheritdoc />
public Task<Emote> GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null)
=> ClientHelper.GetApplicationEmojiAsync(this, emoteId, options);

/// <inheritdoc />
public Task<IReadOnlyCollection<Emote>> GetApplicationEmotesAsync(RequestOptions options = null)
=> ClientHelper.GetApplicationEmojisAsync(this, options);

/// <inheritdoc />
public Task<Emote> ModifyApplicationEmoteAsync(ulong emoteId, Action<ApplicationEmoteProperties> args, RequestOptions options = null)
=> ClientHelper.ModifyApplicationEmojiAsync(this, emoteId, args, options);

/// <inheritdoc />
public Task<Emote> CreateApplicationEmoteAsync(string name, Image image, RequestOptions options = null)
=> ClientHelper.CreateApplicationEmojiAsync(this, name, image, options);

/// <inheritdoc />
public Task DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options = null)
=> ClientHelper.DeleteApplicationEmojiAsync(this, emoteId, options);

#endregion

#region IDiscordClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
17 changes: 13 additions & 4 deletions src/Discord.Net.Rest/Extensions/EntityExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand All @@ -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());

Expand Down
20 changes: 20 additions & 0 deletions src/Discord.Net.WebSocket/DiscordSocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,26 @@ public Task<IReadOnlyCollection<SKU>> GetSKUsAsync(RequestOptions options = null
public Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null)
=> ClientHelper.ConsumeEntitlementAsync(this, entitlementId, options);

/// <inheritdoc />
public Task<Emote> GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null)
=> ClientHelper.GetApplicationEmojiAsync(this, emoteId, options);

/// <inheritdoc />
public Task<IReadOnlyCollection<Emote>> GetApplicationEmotesAsync(RequestOptions options = null)
=> ClientHelper.GetApplicationEmojisAsync(this, options);

/// <inheritdoc />
public Task<Emote> ModifyApplicationEmoteAsync(ulong emoteId, Action<ApplicationEmoteProperties> args, RequestOptions options = null)
=> ClientHelper.ModifyApplicationEmojiAsync(this, emoteId, args, options);

/// <inheritdoc />
public Task<Emote> CreateApplicationEmoteAsync(string name, Image image, RequestOptions options = null)
=> ClientHelper.CreateApplicationEmojiAsync(this, name, image, options);

/// <inheritdoc />
public Task DeleteApplicationEmoteAsync(ulong emoteId, RequestOptions options = null)
=> ClientHelper.DeleteApplicationEmojiAsync(this, emoteId, options);

/// <summary>
/// Gets entitlements from cache.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down