From 52c69c95cb97d2c3f4094006eccddbe644543bbb Mon Sep 17 00:00:00 2001 From: Jonas <46222147+yoyosource@users.noreply.github.com> Date: Sun, 3 Nov 2024 15:11:37 +0100 Subject: [PATCH] Add support for Application Emoji (#2726) --- src/main/java/net/dv8tion/jda/api/JDA.java | 60 ++++++ .../api/entities/emoji/ApplicationEmoji.java | 84 ++++++++ .../jda/api/entities/emoji/CustomEmoji.java | 2 + .../dv8tion/jda/api/entities/emoji/Emoji.java | 3 +- .../jda/api/entities/emoji/EmojiUnion.java | 25 ++- .../api/managers/ApplicationEmojiManager.java | 105 ++++++++++ .../net/dv8tion/jda/api/requests/Route.java | 5 + .../net/dv8tion/jda/internal/JDAImpl.java | 58 ++++++ .../jda/internal/entities/EntityBuilder.java | 10 + .../jda/internal/entities/GuildImpl.java | 3 +- .../entities/emoji/ApplicationEmojiImpl.java | 195 ++++++++++++++++++ .../entities/emoji/CustomEmojiImpl.java | 16 ++ .../entities/emoji/RichCustomEmojiImpl.java | 15 ++ .../entities/emoji/UnicodeEmojiImpl.java | 16 ++ .../managers/ApplicationEmojiManagerImpl.java | 90 ++++++++ .../CreateApplicationEmojiTest.java | 95 +++++++++ 16 files changed, 779 insertions(+), 3 deletions(-) create mode 100644 src/main/java/net/dv8tion/jda/api/entities/emoji/ApplicationEmoji.java create mode 100644 src/main/java/net/dv8tion/jda/api/managers/ApplicationEmojiManager.java create mode 100644 src/main/java/net/dv8tion/jda/internal/entities/emoji/ApplicationEmojiImpl.java create mode 100644 src/main/java/net/dv8tion/jda/internal/managers/ApplicationEmojiManagerImpl.java create mode 100644 src/test/java/net/dv8tion/jda/test/restaction/CreateApplicationEmojiTest.java diff --git a/src/main/java/net/dv8tion/jda/api/JDA.java b/src/main/java/net/dv8tion/jda/api/JDA.java index a0d9b978b5..7f50e3b45c 100644 --- a/src/main/java/net/dv8tion/jda/api/JDA.java +++ b/src/main/java/net/dv8tion/jda/api/JDA.java @@ -23,6 +23,8 @@ import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.entities.emoji.ApplicationEmoji; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; import net.dv8tion.jda.api.entities.sticker.*; import net.dv8tion.jda.api.events.GenericEvent; @@ -1750,6 +1752,64 @@ default List getEmojisByName(@Nonnull String name, boolean igno return getEmojiCache().getElementsByName(name, ignoreCase); } + /** + * Creates a new {@link ApplicationEmoji} for this bot. + * + *

Note that the bot is limited to {@value ApplicationEmoji#APPLICATION_EMOJI_CAP} Application Emojis (normal and animated). + * + * @param name + * The name for the new emoji (2-{@value CustomEmoji#EMOJI_NAME_MAX_LENGTH} characters) + * @param icon + * The {@link Icon} for the new emoji + * + * @throws IllegalArgumentException + * If null is provided or the name is not alphanumeric or not between 2 and {@value CustomEmoji#EMOJI_NAME_MAX_LENGTH} characters long + * + * @return {@link RestAction} - Type: {@link ApplicationEmoji} + */ + @Nonnull + @CheckReturnValue + RestAction createApplicationEmoji(@Nonnull String name, @Nonnull Icon icon); + + /** + * Retrieves a list of Application Emojis together with their respective creators. + * + * @return {@link RestAction RestAction} - Type: List of {@link ApplicationEmoji} + */ + @Nonnull + @CheckReturnValue + RestAction> retrieveApplicationEmojis(); + + /** + * Retrieves an application emoji together with its respective creator. + * + * @param emojiId + * The emoji id + * + * @return {@link RestAction RestAction} - Type: {@link ApplicationEmoji} + */ + @Nonnull + @CheckReturnValue + default RestAction retrieveApplicationEmojiById(long emojiId) + { + return retrieveApplicationEmojiById(Long.toUnsignedString(emojiId)); + } + + /** + * Retrieves an application emoji together with its respective creator. + * + * @param emojiId + * The emoji id + * + * @throws IllegalArgumentException + * If the provided id is not a valid snowflake + * + * @return {@link RestAction RestAction} - Type: {@link ApplicationEmoji} + */ + @Nonnull + @CheckReturnValue + RestAction retrieveApplicationEmojiById(@Nonnull String emojiId); + /** * Attempts to retrieve a {@link Sticker} object based on the provided snowflake reference. *
This works for both {@link StandardSticker} and {@link GuildSticker}, and you can resolve them using the provided {@link StickerUnion}. diff --git a/src/main/java/net/dv8tion/jda/api/entities/emoji/ApplicationEmoji.java b/src/main/java/net/dv8tion/jda/api/entities/emoji/ApplicationEmoji.java new file mode 100644 index 0000000000..1b00e8c4fa --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/emoji/ApplicationEmoji.java @@ -0,0 +1,84 @@ +/* + * Copyright 2024 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.api.entities.emoji; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Icon; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.managers.ApplicationEmojiManager; +import net.dv8tion.jda.api.requests.RestAction; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Represents a Custom Emoji hosted on the Bot Account. + * + *

This does not represent unicode emojis like they are used in the official client! + * The format {@code :smiley:} is a client-side alias which is replaced by the unicode emoji, not a custom emoji. + * + * @see JDA#createApplicationEmoji(String, Icon) + * @see JDA#retrieveApplicationEmojiById(long) + * @see JDA#retrieveApplicationEmojis() + */ +public interface ApplicationEmoji extends CustomEmoji +{ + int APPLICATION_EMOJI_CAP = 2000; + + /** + * The {@link net.dv8tion.jda.api.JDA JDA} instance of this emoji + * + * @return The JDA instance of this emoji + */ + @Nonnull + JDA getJDA(); + + /** + * The user who created this emoji + * + * @return The user who created this emoji + */ + @Nullable + User getOwner(); + + /** + * Deletes this emoji. + * + *

Possible ErrorResponses include: + *

+ * + * @return {@link net.dv8tion.jda.api.requests.RestAction RestAction} + * The RestAction to delete this emoji. + */ + @Nonnull + @CheckReturnValue + RestAction delete(); + + /** + * The {@link ApplicationEmojiManager Manager} for this emoji, used to modify + * properties of the emoji like name. + * + * @return The ApplicationEmojiManager for this emoji + */ + @Nonnull + @CheckReturnValue + ApplicationEmojiManager getManager(); +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/emoji/CustomEmoji.java b/src/main/java/net/dv8tion/jda/api/entities/emoji/CustomEmoji.java index 3b4a7abc36..b3e1183d0f 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/emoji/CustomEmoji.java +++ b/src/main/java/net/dv8tion/jda/api/entities/emoji/CustomEmoji.java @@ -38,6 +38,8 @@ */ public interface CustomEmoji extends Emoji, IMentionable { + int EMOJI_NAME_MAX_LENGTH = 32; + /** Template for {@link #getImageUrl()} */ String ICON_URL = "https://cdn.discordapp.com/emojis/%s.%s"; diff --git a/src/main/java/net/dv8tion/jda/api/entities/emoji/Emoji.java b/src/main/java/net/dv8tion/jda/api/entities/emoji/Emoji.java index 542b7a87f0..8faeed59f6 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/emoji/Emoji.java +++ b/src/main/java/net/dv8tion/jda/api/entities/emoji/Emoji.java @@ -233,8 +233,9 @@ enum Type */ UNICODE, /** - * Custom Guild Emoji. + * Custom Guild Emoji or Custom Application Emoji. *
This represents emojis which were created by users and added to a guild. + *
This can also represent emojis which were created and owned by a specific application. */ CUSTOM, } diff --git a/src/main/java/net/dv8tion/jda/api/entities/emoji/EmojiUnion.java b/src/main/java/net/dv8tion/jda/api/entities/emoji/EmojiUnion.java index 18ae0f55cb..9f487c90bb 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/emoji/EmojiUnion.java +++ b/src/main/java/net/dv8tion/jda/api/entities/emoji/EmojiUnion.java @@ -22,7 +22,8 @@ * Represents possible {@link Emoji} types. * *

This delegates the emoji methods for some concrete emoji type, - * but can be converted to a concrete type using either {@link #asUnicode()} or {@link #asCustom()}. + * but can be converted to a concrete type using either {@link #asUnicode()}, + * {@link #asCustom()}, {@link #asRich()} or {@link #asApplication()}. */ public interface EmojiUnion extends Emoji { @@ -47,4 +48,26 @@ public interface EmojiUnion extends Emoji */ @Nonnull CustomEmoji asCustom(); + + /** + * Returns the underlying {@link RichCustomEmoji} if applicable. + * + * @throws IllegalStateException + * If this is not a {@link RichCustomEmoji} + * + * @return The {@link RichCustomEmoji} + */ + @Nonnull + RichCustomEmoji asRich(); + + /** + * Returns the underlying {@link ApplicationEmoji} if applicable. + * + * @throws IllegalStateException + * If this is not a {@link ApplicationEmoji} + * + * @return The {@link ApplicationEmoji} + */ + @Nonnull + ApplicationEmoji asApplication(); } diff --git a/src/main/java/net/dv8tion/jda/api/managers/ApplicationEmojiManager.java b/src/main/java/net/dv8tion/jda/api/managers/ApplicationEmojiManager.java new file mode 100644 index 0000000000..1d8e1def62 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/managers/ApplicationEmojiManager.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.api.managers; + +import net.dv8tion.jda.api.entities.emoji.ApplicationEmoji; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; + +/** + * Manager providing functionality to update the name field for an {@link ApplicationEmoji}. + * + *

Example + *

{@code
+ * manager.setName("minn")
+ *        .queue();
+ * }
+ * + * @see ApplicationEmoji#getManager() + */ +public interface ApplicationEmojiManager extends Manager +{ + /** Used to reset the name field */ + long NAME = 1; + + /** + * Resets the fields specified by the provided bit-flag pattern. + * You can specify a combination by using a bitwise OR concat of the flag constants. + * + *

Flag Constants: + *

    + *
  • {@link #NAME}
  • + *
+ * + * @param fields + * Integer value containing the flags to reset. + * + * @return ApplicationEmojiManager for chaining convenience. + */ + @Nonnull + @Override + @CheckReturnValue + ApplicationEmojiManager reset(long fields); + + /** + * Resets the fields specified by the provided bit-flag patterns. + * + *

Flag Constants: + *

    + *
  • {@link #NAME}
  • + *
+ * + * @param fields + * Integer values containing the flags to reset. + * + * @return ApplicationEmojiManager for chaining convenience. + */ + @Nonnull + @Override + @CheckReturnValue + ApplicationEmojiManager reset(long... fields); + + /** + * The target {@link ApplicationEmoji} that will be modified by this Manager + * + * @return The target emoji + */ + @Nonnull + ApplicationEmoji getEmoji(); + + /** + * Sets the name of the selected {@link ApplicationEmoji}. + * + *

An emoji name must be between 2-{@value CustomEmoji#EMOJI_NAME_MAX_LENGTH} characters long! + *
Emoji names may only be populated with alphanumeric (with underscore and dash). + * + *

Example: {@code tatDab} or {@code fmgSUP} + * + * @param name + * The new name for the selected {@link ApplicationEmoji} + * + * @throws IllegalArgumentException + * If the provided name is null, not alphanumeric, or not between 2 and {@value CustomEmoji#EMOJI_NAME_MAX_LENGTH} characters long + * + * @return ApplicationEmojiManager for chaining convenience. + */ + @Nonnull + @CheckReturnValue + ApplicationEmojiManager setName(@Nonnull String name); +} diff --git a/src/main/java/net/dv8tion/jda/api/requests/Route.java b/src/main/java/net/dv8tion/jda/api/requests/Route.java index 0e165f9f85..b92d493cf2 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/Route.java +++ b/src/main/java/net/dv8tion/jda/api/requests/Route.java @@ -50,6 +50,11 @@ public static class Applications public static final Route CONSUME_ENTITLEMENT = new Route(POST, "applications/{application_id}/entitlements/{entitlement_id}/consume"); public static final Route CREATE_TEST_ENTITLEMENT = new Route(POST, "applications/{application_id}/entitlements"); public static final Route DELETE_TEST_ENTITLEMENT = new Route(DELETE, "applications/{application_id}/entitlements/{entitlement_id}"); + public static final Route GET_APPLICATION_EMOJIS = new Route(GET, "applications/{application_id}/emojis"); + public static final Route GET_APPLICATION_EMOJI = new Route(GET, "applications/{application_id}/emojis/{emoji_id}"); + public static final Route CREATE_APPLICATION_EMOJI = new Route(POST, "applications/{application_id}/emojis"); + public static final Route MODIFY_APPLICATION_EMOJI = new Route(PATCH, "applications/{application_id}/emojis/{emoji_id}"); + public static final Route DELETE_APPLICATION_EMOJI = new Route(DELETE, "applications/{application_id}/emojis/{emoji_id}"); } public static class Interactions diff --git a/src/main/java/net/dv8tion/jda/internal/JDAImpl.java b/src/main/java/net/dv8tion/jda/internal/JDAImpl.java index 8ef202f4d3..9c1be36130 100644 --- a/src/main/java/net/dv8tion/jda/internal/JDAImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/JDAImpl.java @@ -29,6 +29,8 @@ import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.*; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.entities.emoji.ApplicationEmoji; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; import net.dv8tion.jda.api.entities.sticker.StickerPack; import net.dv8tion.jda.api.entities.sticker.StickerSnowflake; @@ -675,6 +677,62 @@ public SnowflakeCacheView getEmojiCache() return CacheView.allSnowflakes(() -> guildCache.stream().map(Guild::getEmojiCache)); } + @Nonnull + @Override + public RestAction createApplicationEmoji(@Nonnull String name, @Nonnull Icon icon) + { + Checks.inRange(name, 2, CustomEmoji.EMOJI_NAME_MAX_LENGTH, "Emoji name"); + Checks.matches(name, Checks.ALPHANUMERIC_WITH_DASH, "Emoji name"); + Checks.notNull(icon, "Emoji icon"); + + DataObject body = DataObject.empty(); + body.put("name", name); + body.put("image", icon.getEncoding()); + + final Route.CompiledRoute route = Route.Applications.CREATE_APPLICATION_EMOJI.compile(getSelfUser().getApplicationId()); + return new RestActionImpl<>(this, route, body, (response, request) -> + { + final DataObject obj = response.getObject(); + return entityBuilder.createApplicationEmoji(this, obj); + }); + } + + @Nonnull + @Override + public RestAction> retrieveApplicationEmojis() + { + Route.CompiledRoute route = Route.Applications.GET_APPLICATION_EMOJIS.compile(getSelfUser().getApplicationId()); + return new RestActionImpl<>(this, route, (response, request) -> + { + DataArray emojis = response.getObject().getArray("items"); + List list = new ArrayList<>(emojis.length()); + for (int i = 0; i < emojis.length(); i++) + { + try + { + list.add(entityBuilder.createApplicationEmoji(this, emojis.getObject(i))); + } + catch (ParsingException e) + { + LOG.error("Failed to parse application emoji with JSON: {}", emojis.getObject(i), e); + } + } + + return Collections.unmodifiableList(list); + }); + } + + @Nonnull + @Override + public RestAction retrieveApplicationEmojiById(@Nonnull String emojiId) + { + Checks.isSnowflake(emojiId); + Route.CompiledRoute route = Route.Applications.GET_APPLICATION_EMOJI.compile(getSelfUser().getApplicationId(), emojiId); + return new RestActionImpl<>(this, route, + (response, request) -> entityBuilder.createApplicationEmoji(this, response.getObject()) + ); + } + @Nonnull @Override public RestAction retrieveSticker(@Nonnull StickerSnowflake sticker) diff --git a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java index 2a8341eb23..7fb94a3abe 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java @@ -66,6 +66,7 @@ import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IPermissionContainerMixin; import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IPostContainerMixin; import net.dv8tion.jda.internal.entities.channel.mixin.middleman.AudioChannelMixin; +import net.dv8tion.jda.internal.entities.emoji.ApplicationEmojiImpl; import net.dv8tion.jda.internal.entities.emoji.CustomEmojiImpl; import net.dv8tion.jda.internal.entities.emoji.RichCustomEmojiImpl; import net.dv8tion.jda.internal.entities.emoji.UnicodeEmojiImpl; @@ -1020,6 +1021,15 @@ public RichCustomEmojiImpl createEmoji(GuildImpl guildObj, DataObject json) .setAvailable(json.getBoolean("available", true)); } + public ApplicationEmojiImpl createApplicationEmoji(JDAImpl api, DataObject json) + { + final long emojiId = json.getUnsignedLong("id"); + final User user = createUser(json.getObject("user")); + return new ApplicationEmojiImpl(emojiId, api, user) + .setAnimated(json.getBoolean("animated")) + .setName(json.getString("name")); + } + public ScheduledEvent createScheduledEvent(GuildImpl guild, DataObject json) { final long id = json.getLong("id"); diff --git a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java index 0afde36341..511ae41d7d 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java @@ -33,6 +33,7 @@ import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; import net.dv8tion.jda.api.entities.sticker.GuildSticker; import net.dv8tion.jda.api.entities.sticker.StandardSticker; @@ -1875,7 +1876,7 @@ public RoleAction createRole() public AuditableRestAction createEmoji(@Nonnull String name, @Nonnull Icon icon, @Nonnull Role... roles) { checkPermission(Permission.MANAGE_GUILD_EXPRESSIONS); - Checks.inRange(name, 2, 32, "Emoji name"); + Checks.inRange(name, 2, CustomEmoji.EMOJI_NAME_MAX_LENGTH, "Emoji name"); Checks.notNull(icon, "Emoji icon"); Checks.notNull(roles, "Roles"); diff --git a/src/main/java/net/dv8tion/jda/internal/entities/emoji/ApplicationEmojiImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/emoji/ApplicationEmojiImpl.java new file mode 100644 index 0000000000..b5a6c5782b --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/entities/emoji/ApplicationEmojiImpl.java @@ -0,0 +1,195 @@ +/* + * Copyright 2024 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.internal.entities.emoji; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.emoji.ApplicationEmoji; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji; +import net.dv8tion.jda.api.managers.ApplicationEmojiManager; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.requests.Route; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.internal.JDAImpl; +import net.dv8tion.jda.internal.managers.ApplicationEmojiManagerImpl; +import net.dv8tion.jda.internal.requests.RestActionImpl; +import net.dv8tion.jda.internal.utils.EntityString; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ApplicationEmojiImpl implements ApplicationEmoji, EmojiUnion +{ + private final long id; + private final JDAImpl api; + private final User owner; + + boolean animated = false; + private String name; + + public ApplicationEmojiImpl(long id, JDAImpl api, User owner) + { + this.id = id; + this.api = api; + this.owner = owner; + } + + @Nonnull + @Override + public Type getType() + { + return Type.CUSTOM; + } + + @Nonnull + @Override + public String getAsReactionCode() + { + return name + ":" + id; + } + + @Nonnull + @Override + public DataObject toData() + { + return DataObject.empty() + .put("name", name) + .put("animated", animated) + .put("id", id); + } + + @Nonnull + @Override + public String getName() + { + return name; + } + + @Override + public long getIdLong() + { + return id; + } + + @Nonnull + @Override + public JDA getJDA() + { + return api; + } + + @Nullable + @Override + public User getOwner() + { + return owner; + } + + @Nonnull + @Override + public ApplicationEmojiManager getManager() + { + return new ApplicationEmojiManagerImpl(this); + } + + @Override + public boolean isAnimated() + { + return animated; + } + + @Nonnull + @Override + public RestAction delete() + { + Route.CompiledRoute route = Route.Applications.DELETE_APPLICATION_EMOJI.compile(getJDA().getSelfUser().getApplicationId(), getId()); + return new RestActionImpl<>(getJDA(), route); + } + + // -- Setters -- + + public ApplicationEmojiImpl setName(String name) + { + this.name = name; + return this; + } + + public ApplicationEmojiImpl setAnimated(boolean animated) + { + this.animated = animated; + return this; + } + + // -- Object overrides -- + + @Override + public boolean equals(Object obj) + { + if (obj == this) + return true; + if (!(obj instanceof ApplicationEmojiImpl)) + return false; + + ApplicationEmojiImpl other = (ApplicationEmojiImpl) obj; + return this.id == other.getIdLong(); + } + + @Override + public int hashCode() + { + return Long.hashCode(id); + } + + @Override + public String toString() + { + return new EntityString(this) + .setName(name) + .toString(); + } + + @Nonnull + @Override + public UnicodeEmoji asUnicode() + { + throw new IllegalStateException("Cannot convert ApplicationEmoji to UnicodeEmoji!"); + } + + @Nonnull + @Override + public CustomEmoji asCustom() + { + return this; + } + + @Nonnull + @Override + public RichCustomEmoji asRich() + { + throw new IllegalStateException("Cannot convert ApplicationEmoji to RichCustomEmoji!"); + } + + @Nonnull + @Override + public ApplicationEmoji asApplication() + { + return this; + } +} diff --git a/src/main/java/net/dv8tion/jda/internal/entities/emoji/CustomEmojiImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/emoji/CustomEmojiImpl.java index 9f036acbc5..5ae051710a 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/emoji/CustomEmojiImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/emoji/CustomEmojiImpl.java @@ -16,7 +16,9 @@ package net.dv8tion.jda.internal.entities.emoji; +import net.dv8tion.jda.api.entities.emoji.ApplicationEmoji; import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; import net.dv8tion.jda.api.entities.emoji.EmojiUnion; import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji; import net.dv8tion.jda.api.utils.data.DataObject; @@ -125,4 +127,18 @@ public CustomEmoji asCustom() { return this; } + + @Nonnull + @Override + public RichCustomEmoji asRich() + { + throw new IllegalStateException("Cannot convert CustomEmoji to RichCustomEmoji!"); + } + + @Nonnull + @Override + public ApplicationEmoji asApplication() + { + throw new IllegalStateException("Cannot convert CustomEmoji to ApplicationEmoji!"); + } } diff --git a/src/main/java/net/dv8tion/jda/internal/entities/emoji/RichCustomEmojiImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/emoji/RichCustomEmojiImpl.java index 28c2921847..3330e1d34e 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/emoji/RichCustomEmojiImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/emoji/RichCustomEmojiImpl.java @@ -19,6 +19,7 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.emoji.ApplicationEmoji; import net.dv8tion.jda.api.entities.emoji.CustomEmoji; import net.dv8tion.jda.api.entities.emoji.EmojiUnion; import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; @@ -279,4 +280,18 @@ public CustomEmoji asCustom() { return this; } + + @Nonnull + @Override + public RichCustomEmoji asRich() + { + return this; + } + + @Nonnull + @Override + public ApplicationEmoji asApplication() + { + throw new IllegalStateException("Cannot convert RichCustomEmoji to ApplicationEmoji!"); + } } diff --git a/src/main/java/net/dv8tion/jda/internal/entities/emoji/UnicodeEmojiImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/emoji/UnicodeEmojiImpl.java index 71eda48bcd..aecf0f63a7 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/emoji/UnicodeEmojiImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/emoji/UnicodeEmojiImpl.java @@ -16,7 +16,9 @@ package net.dv8tion.jda.internal.entities.emoji; +import net.dv8tion.jda.api.entities.emoji.ApplicationEmoji; import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; import net.dv8tion.jda.api.entities.emoji.EmojiUnion; import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji; import net.dv8tion.jda.api.utils.data.DataObject; @@ -100,4 +102,18 @@ public CustomEmoji asCustom() { throw new IllegalStateException("Cannot convert UnicodeEmoji into CustomEmoji!"); } + + @Nonnull + @Override + public RichCustomEmoji asRich() + { + throw new IllegalStateException("Cannot convert UnicodeEmoji into RichCustomEmoji!"); + } + + @Nonnull + @Override + public ApplicationEmoji asApplication() + { + throw new IllegalStateException("Cannot convert UnicodeEmoji to ApplicationEmoji!"); + } } diff --git a/src/main/java/net/dv8tion/jda/internal/managers/ApplicationEmojiManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/ApplicationEmojiManagerImpl.java new file mode 100644 index 0000000000..1a57920104 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/managers/ApplicationEmojiManagerImpl.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.internal.managers; + +import net.dv8tion.jda.api.entities.emoji.ApplicationEmoji; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.managers.ApplicationEmojiManager; +import net.dv8tion.jda.api.requests.Route; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.internal.utils.Checks; +import okhttp3.RequestBody; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; + +public class ApplicationEmojiManagerImpl extends ManagerBase implements ApplicationEmojiManager +{ + protected final ApplicationEmoji emoji; + + protected String name; + + public ApplicationEmojiManagerImpl(ApplicationEmoji emoji) + { + super(emoji.getJDA(), Route.Applications.MODIFY_APPLICATION_EMOJI.compile(emoji.getJDA().getSelfUser().getApplicationId(), emoji.getId())); + this.emoji = emoji; + } + + @NotNull + @Override + public ApplicationEmoji getEmoji() + { + return emoji; + } + + @Nonnull + @Override + @CheckReturnValue + public ApplicationEmojiManagerImpl reset(long fields) + { + super.reset(fields); + if ((fields & NAME) == NAME) + this.name = null; + return this; + } + + @Nonnull + @Override + @CheckReturnValue + public ApplicationEmojiManagerImpl reset(long... fields) + { + super.reset(fields); + return this; + } + + @NotNull + @Override + public ApplicationEmojiManager setName(@NotNull String name) + { + Checks.inRange(name, 2, CustomEmoji.EMOJI_NAME_MAX_LENGTH, "Emoji name"); + Checks.matches(name, Checks.ALPHANUMERIC_WITH_DASH, "Emoji name"); + this.name = name; + set |= NAME; + return this; + } + + @Override + protected RequestBody finalizeData() + { + DataObject object = DataObject.empty(); + if (shouldUpdate(NAME)) + object.put("name", name); + reset(); + return getRequestBody(object); + } +} diff --git a/src/test/java/net/dv8tion/jda/test/restaction/CreateApplicationEmojiTest.java b/src/test/java/net/dv8tion/jda/test/restaction/CreateApplicationEmojiTest.java new file mode 100644 index 0000000000..7281970fbc --- /dev/null +++ b/src/test/java/net/dv8tion/jda/test/restaction/CreateApplicationEmojiTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.test.restaction; + +import net.dv8tion.jda.api.entities.Icon; +import net.dv8tion.jda.api.requests.Method; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.internal.entities.SelfUserImpl; +import net.dv8tion.jda.test.Constants; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@SuppressWarnings("ResultOfMethodCallIgnored") +public class CreateApplicationEmojiTest extends RestActionTest +{ + private static final String EXAMPLE_NAME = "thinking"; + private static final Icon EXAMPLE_ICON = Icon.from(new byte[]{1, 2, 3}); + + @BeforeEach + void setupMocks() + { + when(jda.createApplicationEmoji(any(), any())).thenCallRealMethod(); + when(jda.getSelfUser()).thenReturn(new SelfUserImpl(Constants.BUTLER_USER_ID, jda)); + } + + @MethodSource + @ParameterizedTest + void testNullArguments(String name, Icon icon) + { + assertThatIllegalArgumentException() + .isThrownBy(() -> jda.createApplicationEmoji(name, icon)); + } + + static Stream testNullArguments() + { + return Stream.of( + arguments(null, null), + arguments(null, EXAMPLE_ICON), + arguments(EXAMPLE_NAME, null) + ); + } + + @Test + void testWrongNameFormat() + { + assertThatIllegalArgumentException() + .isThrownBy(() -> jda.createApplicationEmoji("test with spaces", EXAMPLE_ICON)) + .withMessageContaining("must match regex"); + } + + @Test + void testWrongNameLength() + { + assertThatIllegalArgumentException() + .isThrownBy(() -> jda.createApplicationEmoji(StringUtils.repeat('a', 33), EXAMPLE_ICON)) + .withMessageContaining("must be between 2 and 32 characters long"); + } + + @Test + void testValidEmoji() + { + assertThatRequestFrom(jda.createApplicationEmoji(EXAMPLE_NAME, EXAMPLE_ICON)) + .hasMethod(Method.POST) + .hasCompiledRoute("applications/" + Constants.BUTLER_USER_ID + "/emojis") + .hasBodyEqualTo(DataObject.empty() + .put("name", EXAMPLE_NAME) + .put("image", EXAMPLE_ICON.getEncoding())) + .whenQueueCalled(); + } +}