diff --git a/src/main/java/net/dv8tion/jda/api/entities/Invite.java b/src/main/java/net/dv8tion/jda/api/entities/Invite.java index 291c1cd3a1..6a2899e13c 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Invite.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Invite.java @@ -137,11 +137,21 @@ static RestAction resolve(@Nonnull final JDA api, @Nonnull final String /** * The type of this invite. * - * @return The invites's type + * @return The invite's type */ @Nonnull Invite.InviteType getType(); + /** + * The target type of this invite or {@link TargetType#NONE} if this invite does not have a {@link #getTarget() InviteTarget}. + * + * @return The invite's target type or {@link TargetType#NONE} + * + * @see Invite.TargetType + */ + @Nonnull + Invite.TargetType getTargetType(); + /** * An {@link net.dv8tion.jda.api.entities.Invite.Channel Invite.Channel} object * containing information about this invite's origin channel. @@ -153,14 +163,6 @@ static RestAction resolve(@Nonnull final JDA api, @Nonnull final String @Nullable Channel getChannel(); - /** - * The invite code - * - * @return the invite code - */ - @Nonnull - String getCode(); - /** * An {@link net.dv8tion.jda.api.entities.Invite.Group Invite.Group} object * containing information about this invite's origin group. @@ -172,6 +174,26 @@ static RestAction resolve(@Nonnull final JDA api, @Nonnull final String @Nullable Group getGroup(); + /** + * An {@link Invite.InviteTarget Invite.InviteTarget} object + * containing information about this invite's target or {@code null} + * if this invite does not have a target. + * + * @return Information about this invite's target or {@code null} + * + * @see net.dv8tion.jda.api.entities.Invite.InviteTarget + */ + @Nullable + InviteTarget getTarget(); + + /** + * The invite code + * + * @return the invite code + */ + @Nonnull + String getCode(); + /** * The invite URL for this invite in the format of: * {@code "https://discord.gg/" + getCode()} @@ -329,7 +351,7 @@ interface Channel extends ISnowflake /** * The name of this channel. * - * @return The channels's name + * @return The channel's name */ @Nonnull String getName(); @@ -374,7 +396,7 @@ interface Guild extends ISnowflake /** * The name of this guild. * - * @return The guilds's name + * @return The guild's name */ @Nonnull String getName(); @@ -486,12 +508,129 @@ interface Group extends ISnowflake * {@link #resolve(net.dv8tion.jda.api.JDA, java.lang.String, boolean) Invite.resolve()} method with the * {@code withCounts} boolean set to {@code true}. * - * @return The names of the groups's users or null if not preset in the invite + * @return The names of the group's users or null if not preset in the invite */ @Nullable List getUsers(); } + /** + * POJO for the target of this invite. + * + * @see #getTarget() + */ + interface InviteTarget + { + + /** + * The type of this invite target. + * + * @return The type of this invite target + */ + @Nonnull + TargetType getType(); + + /** + * The Snowflake id of the target entity of this invite. + * + * @throws IllegalStateException + * If there is no target entity, {@link #getType() TargetType} is {@link TargetType#UNKNOWN} + * + * @return The id of the target entity + */ + @Nonnull + String getId(); + + /** + * The Snowflake id of the target entity of this invite. + * + * @throws IllegalStateException + * If there is no target entity, {@link #getType() TargetType} is {@link TargetType#UNKNOWN} + * + * @return The id of the target entity + */ + long getIdLong(); + + /** + * The target {@link User} of this invite or {@code null} if the {@link #getType() TargeType} is not {@link TargetType#STREAM} + * + * @return The target user of this invite + * + * @see net.dv8tion.jda.api.entities.User + */ + @Nullable + User getUser(); + + /** + * The target {@link EmbeddedApplication} of this invite or {@code null} if the {@link #getType() TargeType} is not {@link TargetType#EMBEDDED_APPLICATION} + * + * @return The target application of this invite + * + * @see net.dv8tion.jda.api.entities.Invite.EmbeddedApplication + */ + @Nullable + EmbeddedApplication getApplication(); + } + + /** + * POJO for the target application information provided by an invite. + * + * @see InviteTarget#getApplication() + */ + interface EmbeddedApplication extends ISnowflake + { + /** + * The name of this application. + * + * @return The name of this application. + */ + @Nonnull + String getName(); + + /** + * The description of this application. + * + * @return The description of this application. + */ + @Nonnull + String getDescription(); + + /** + * The summary of this application or {@code null} if this application has no summary. + * + * @return The summary of this application. + */ + @Nullable + String getSummary(); + + /** + * The icon id of this application or {@code null} if the application has no icon. + * + * @return The application's icon id + * + * @see #getIconUrl() + */ + @Nullable + String getIconId(); + + /** + * The icon url of this application or {@code null} if the application has no icon. + * + * @return The application's icon url + * + * @see #getIconId() + */ + @Nullable + String getIconUrl(); + + /** + * The max participant count of this application or {@code -1} if no max participant count is set + * + * @return {@code -1} if this application does not have a max participant count + */ + int getMaxParticipants(); + } + /** * Enum representing the type of an invite. * @@ -503,4 +642,78 @@ enum InviteType GROUP, UNKNOWN } + + /** + * A TargetType indicates additional action to be taken by the client on accepting the invite, + * typically connecting external services or launching external applications depending on the specific TargetType. + * + * Some actions might not be available or show up on certain devices. + * + * @see InviteTarget#getType() + */ + enum TargetType + { + /** + * The invite does not have a target type, {@link Invite#getTarget()} will return {@code null}. + */ + NONE(0), + + /** + * The invite points to a user's stream in a voice channel. + * The user to whose stream the invite goes can be get with {@link InviteTarget#getUser() InviteTarget.getUser} and is not {@code null}. + * + * @see InviteTarget#getUser() + */ + STREAM(1), + + /** + * The invite points to an application in a voice channel. + * The application to which the invite goes can be get with {@link InviteTarget#getApplication() InviteTarget.getApplication} and is not {@code null}. + * + * @see InviteTarget#getApplication() + */ + EMBEDDED_APPLICATION(2), + + /** + * Unknown Discord invite target type. Should never happen and would only possibly happen if Discord implemented a new + * target type and JDA had yet to implement support for it. + */ + UNKNOWN(-1); + + private final int id; + + TargetType(int id) + { + this.id = id; + } + + /** + * The Discord id key used to represent the target type. + * + * @return The id key used by discord for this channel type. + */ + public int getId() + { + return id; + } + + /** + * Static accessor for retrieving a target type based on its Discord id key. + * + * @param id + * The id key of the requested target type. + * + * @return The TargetType that is referred to by the provided key. If the id key is unknown, {@link #UNKNOWN} is returned. + */ + @Nonnull + public static TargetType fromId(int id) + { + for (TargetType type : values()) + { + if (type.id == id) + return type; + } + return UNKNOWN; + } + } } diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/InviteAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/InviteAction.java index dd1cf35acd..158b6bc3e4 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/restaction/InviteAction.java +++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/InviteAction.java @@ -18,6 +18,10 @@ import net.dv8tion.jda.api.entities.GuildChannel; import net.dv8tion.jda.api.entities.Invite; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.utils.MiscUtil; +import net.dv8tion.jda.internal.utils.Checks; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; @@ -118,4 +122,119 @@ public interface InviteAction extends AuditableRestAction @Nonnull @CheckReturnValue InviteAction setUnique(@Nullable final Boolean unique); + + /** + * Sets the id of the targeted application. + *
The invite has to point to a voice channel. + * The invite will have the {@link Invite.TargetType#EMBEDDED_APPLICATION} target. + * + * @param applicationId + * The id of the embedded application to target or {@code 0} to remove + * + * @return The current InviteAction for chaining. + */ + @Nonnull + @CheckReturnValue + InviteAction setTargetApplication(final long applicationId); + + /** + * Sets the id of the targeted application. + *
The invite has to point to a voice channel. + * The invite will have the {@link Invite.TargetType#EMBEDDED_APPLICATION} target. + * + * @param applicationId + * The id of the embedded application to target + * + * @throws java.lang.IllegalArgumentException + * If the provided ID is null + * @throws java.lang.NumberFormatException + * If the provided ID is not a snowflake + * + * @return The current InviteAction for chaining. + */ + @Nonnull + @CheckReturnValue + default InviteAction setTargetApplication(@Nonnull final String applicationId) + { + return setTargetApplication(MiscUtil.parseSnowflake(applicationId)); + } + + /** + * Sets the user whose stream to target for this invite. + *
The user must be streaming in the same channel. + * The invite will have the {@link Invite.TargetType#STREAM} target. + * + * @param userId + * The id of the user whose stream to target or {@code 0} to remove. + * + * @return The current InviteAction for chaining. + */ + @Nonnull + @CheckReturnValue + InviteAction setTargetStream(final long userId); + + /** + * Sets the user whose stream to display for this invite. + *
The user must be streaming in the same channel. + * The invite will have the {@link Invite.TargetType#STREAM} target. + * + * @param userId + * The id of the user whose stream to target. + * + * @throws java.lang.IllegalArgumentException + * If the provided ID is null + * @throws java.lang.NumberFormatException + * If the provided ID is not a snowflake + * + * @return The current InviteAction for chaining. + */ + @Nonnull + @CheckReturnValue + default InviteAction setTargetStream(@Nonnull final String userId) + { + return setTargetStream(MiscUtil.parseSnowflake(userId)); + } + + /** + * Sets the user whose stream to display for this invite. + *
The user must be streaming in the same channel. + * The invite will have the {@link Invite.TargetType#STREAM} target. + * + * @param user + * The user whose stream to target. + * + * @throws IllegalArgumentException + * If the provided user is {@code null} + * + * @return The current InviteAction for chaining. + */ + @Nonnull + @CheckReturnValue + default InviteAction setTargetStream(@Nonnull final User user) + { + Checks.notNull(user, "User"); + return setTargetStream(user.getIdLong()); + } + + /** + * Sets the user whose stream to display for this invite. + *
The user must be streaming in the same channel. + * The invite will have the {@link Invite.TargetType#STREAM} target. + * + * @param member + * The member whose stream to target. + * + * @throws IllegalArgumentException + * If the provided member is {@code null} + * + * @return The current InviteAction for chaining. + */ + @Nonnull + @CheckReturnValue + default InviteAction setTargetStream(@Nonnull final Member member) + { + Checks.notNull(member, "Member"); + return setTargetStream(member.getIdLong()); + } + } 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 21b65a8124..0c52b4bae5 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java @@ -1515,7 +1515,7 @@ public WebhookImpl createWebhook(DataObject object, boolean allowMissingChannel) Optional ownerJson = object.optObject("user"); User owner = null; - + if (ownerJson.isPresent()) { DataObject json = ownerJson.get(); @@ -1556,11 +1556,13 @@ public Invite createInvite(DataObject object) final DataObject channelObject = object.getObject("channel"); final ChannelType channelType = ChannelType.fromId(channelObject.getInt("type")); + final Invite.TargetType targetType = Invite.TargetType.fromId(object.getInt("target_type", 0)); final Invite.InviteType type; final Invite.Guild guild; final Invite.Channel channel; final Invite.Group group; + final Invite.InviteTarget target; if (channelType == ChannelType.GROUP) { @@ -1618,6 +1620,28 @@ else if (channelType.isGuild()) group = null; } + switch (targetType) + { + case EMBEDDED_APPLICATION: + final DataObject applicationObject = object.getObject("target_application"); + + Invite.EmbeddedApplication application = new InviteImpl.EmbeddedApplicationImpl( + applicationObject.getString("icon", null), applicationObject.getString("name"), applicationObject.getString("description"), + applicationObject.getString("summary"), applicationObject.getLong("id"), applicationObject.getInt("max_participants", -1) + ); + target = new InviteImpl.InviteTargetImpl(targetType, application, null); + break; + case STREAM: + final DataObject targetUserObject = object.getObject("target_user"); + target = new InviteImpl.InviteTargetImpl(targetType, null, createUser(targetUserObject)); + break; + case NONE: + target = null; + break; + default: + target = new InviteImpl.InviteTargetImpl(targetType, null, null); + } + final int maxAge; final int maxUses; final boolean temporary; @@ -1645,8 +1669,8 @@ else if (channelType.isGuild()) } return new InviteImpl(getJDA(), code, expanded, inviter, - maxAge, maxUses, temporary, - timeCreated, uses, channel, guild, group, type); + maxAge, maxUses, temporary, timeCreated, + uses, channel, guild, group, target, type); } public ApplicationInfo createApplicationInfo(DataObject object) diff --git a/src/main/java/net/dv8tion/jda/internal/entities/InviteImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/InviteImpl.java index 032c66f2d0..f391ac5365 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/InviteImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/InviteImpl.java @@ -35,6 +35,7 @@ import net.dv8tion.jda.internal.utils.Checks; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.OffsetDateTime; import java.util.List; import java.util.Set; @@ -47,6 +48,7 @@ public class InviteImpl implements Invite private final boolean expanded; private final Guild guild; private final Group group; + private final InviteTarget target; private final User inviter; private final int maxAge; private final int maxUses; @@ -56,8 +58,8 @@ public class InviteImpl implements Invite private final Invite.InviteType type; public InviteImpl(final JDAImpl api, final String code, final boolean expanded, final User inviter, - final int maxAge, final int maxUses, final boolean temporary, final OffsetDateTime timeCreated, - final int uses, final Channel channel, final Guild guild, final Group group, final Invite.InviteType type) + final int maxAge, final int maxUses, final boolean temporary, final OffsetDateTime timeCreated, final int uses, + final Channel channel, final Guild guild, final Group group, final InviteTarget target, final Invite.InviteType type) { this.api = api; this.code = code; @@ -71,6 +73,7 @@ public InviteImpl(final JDAImpl api, final String code, final boolean expanded, this.channel = channel; this.guild = guild; this.group = group; + this.target = target; this.type = type; } @@ -157,6 +160,13 @@ public Invite.InviteType getType() return this.type; } + @Nonnull + @Override + public TargetType getTargetType() + { + return target == null ? TargetType.NONE : target.getType(); + } + @Override public Channel getChannel() { @@ -192,6 +202,13 @@ public Group getGroup() return this.group; } + @Nullable + @Override + public InviteTarget getTarget() + { + return target; + } + @Override public User getInviter() { @@ -312,7 +329,6 @@ public ChannelType getType() { return this.type; } - } public static class GuildImpl implements Guild @@ -410,7 +426,6 @@ public Set getFeatures() public static class GroupImpl implements Group { - private final String iconId, name; private final long id; private final List users; @@ -454,4 +469,126 @@ public List getUsers() return users; } } + + public static class InviteTargetImpl implements InviteTarget + { + private final TargetType type; + private final EmbeddedApplication targetApplication; + private final User targetUser; + + public InviteTargetImpl(TargetType type, EmbeddedApplication targetApplication, User targetUser) + { + this.type = type; + this.targetApplication = targetApplication; + this.targetUser = targetUser; + } + + @Nonnull + @Override + public TargetType getType() + { + return type; + } + + @Nonnull + @Override + public String getId() + { + return getTargetEntity().getId(); + } + + @Override + public long getIdLong() + { + return getTargetEntity().getIdLong(); + } + + @Nullable + @Override + public User getUser() + { + return targetUser; + } + + @Nullable + @Override + public EmbeddedApplication getApplication() + { + return targetApplication; + } + + @Nonnull + private ISnowflake getTargetEntity() + { + if (targetUser != null) return targetUser; + if (targetApplication != null) return targetApplication; + throw new IllegalStateException("No target entity"); + } + + } + + public static class EmbeddedApplicationImpl implements EmbeddedApplication + { + private final String iconId, name, description, summary; + private final long id; + private final int maxParticipants; + + public EmbeddedApplicationImpl(final String iconId, final String name, final String description, final String summary, final long id, final int maxParticipants) + { + this.iconId = iconId; + this.name = name; + this.description = description; + this.summary = summary; + this.id = id; + this.maxParticipants = maxParticipants; + } + + @Override + public long getIdLong() + { + return this.id; + } + + @Nonnull + @Override + public String getName() + { + return this.name; + } + + @Nonnull + @Override + public String getDescription() + { + return this.description; + } + + @Nullable + @Override + public String getSummary() + { + return this.summary; + } + + @Nullable + @Override + public String getIconId() + { + return this.iconId; + } + + @Nullable + @Override + public String getIconUrl() + { + return this.iconId == null ? null + : "https://cdn.discordapp.com/app-icons/" + this.id + '/' + this.iconId + ".png"; + } + + @Override + public int getMaxParticipants() + { + return maxParticipants; + } + } } diff --git a/src/main/java/net/dv8tion/jda/internal/handle/InviteCreateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/InviteCreateHandler.java index 7657351844..7f7ca6f089 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/InviteCreateHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/InviteCreateHandler.java @@ -74,7 +74,33 @@ protected Long handleInternally(DataObject content) InviteImpl.ChannelImpl channel = new InviteImpl.ChannelImpl(realChannel); InviteImpl.GuildImpl guild = new InviteImpl.GuildImpl(realGuild); - Invite invite = new InviteImpl(getJDA(), code, expanded, inviter, maxAge, maxUses, temporary, creationTime, 0, channel, guild, null, Invite.InviteType.GUILD); + final Invite.TargetType targetType = Invite.TargetType.fromId(content.getInt("target_type", 0)); + final Invite.InviteTarget target; + + switch (targetType) + { + case STREAM: + DataObject targetUserObject = content.getObject("target_user"); + target = new InviteImpl.InviteTargetImpl(targetType, null, getJDA().getEntityBuilder().createUser(targetUserObject)); + break; + case EMBEDDED_APPLICATION: + DataObject applicationObject = content.getObject("target_application"); + Invite.EmbeddedApplication application = new InviteImpl.EmbeddedApplicationImpl( + applicationObject.getString("icon", null), applicationObject.getString("name"), applicationObject.getString("description"), + applicationObject.getString("summary"), applicationObject.getLong("id"), applicationObject.getInt("max_participants", -1) + ); + target = new InviteImpl.InviteTargetImpl(targetType, application, null); + break; + case NONE: + target = null; + break; + default: + target = new InviteImpl.InviteTargetImpl(targetType, null, null); + } + + Invite invite = new InviteImpl(getJDA(), code, expanded, inviter, + maxAge, maxUses, temporary, creationTime, + 0, channel, guild, null, target, Invite.InviteType.GUILD); getJDA().handleEvent( new GuildInviteCreateEvent( getJDA(), responseNumber, diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/InviteActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/InviteActionImpl.java index 2774d98698..0d8c2fe108 100644 --- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/InviteActionImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/InviteActionImpl.java @@ -28,6 +28,7 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; @@ -37,6 +38,9 @@ public class InviteActionImpl extends AuditableRestActionImpl implements private Integer maxUses = null; private Boolean temporary = null; private Boolean unique = null; + private Long targetApplication = null; + private Long targetUser = null; + private Invite.TargetType targetType = null; public InviteActionImpl(final JDA api, final String channelId) { @@ -120,6 +124,38 @@ public InviteActionImpl setUnique(final Boolean unique) return this; } + @Nonnull + @Override + public InviteAction setTargetApplication(final long applicationId) + { + if (applicationId == 0) + { + this.targetType = null; + this.targetApplication = null; + return this; + } + + this.targetType = Invite.TargetType.EMBEDDED_APPLICATION; + this.targetApplication = applicationId; + return this; + } + + @Nonnull + @Override + public InviteAction setTargetStream(final long userId) + { + if (userId == 0) + { + this.targetType = null; + this.targetUser = null; + return this; + } + + this.targetType = Invite.TargetType.STREAM; + this.targetUser = userId; + return this; + } + @Override protected RequestBody finalizeData() { @@ -133,6 +169,12 @@ protected RequestBody finalizeData() object.put("temporary", this.temporary); if (this.unique != null) object.put("unique", this.unique); + if (this.targetType != null) + object.put("target_type", targetType.getId()); + if (this.targetUser != null) + object.put("target_user_id", targetUser); + if (this.targetApplication != null) + object.put("target_application_id", targetApplication); return getRequestBody(object); }