diff --git a/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java b/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java index 5b4bfc18af..c124412d32 100644 --- a/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java +++ b/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java @@ -16,9 +16,16 @@ package net.dv8tion.jda.api.audit; +import net.dv8tion.jda.annotations.DeprecatedSince; +import net.dv8tion.jda.annotations.ForRemoval; +import net.dv8tion.jda.annotations.ReplaceWith; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.attribute.ICategorizableChannel; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; @@ -207,6 +214,13 @@ public enum AuditLogKey */ CHANNEL_NAME("name"), + /** + * Change of the {@link Channel#getFlags() flags} value. + * + *

Expected type: Integer + */ + CHANNEL_FLAGS("flags"), + /** * Change of the {@link ICategorizableChannel#getParentCategory()} ICategorizable.getParentCategory()} value. *
Use with {@link net.dv8tion.jda.api.entities.Guild#getCategoryById(String) Guild.getCategoryById(String)} @@ -224,13 +238,26 @@ public enum AuditLogKey CHANNEL_TOPIC("topic"), /** - * Change of the {@link TextChannel#getSlowmode() TextChannel.getSlowmode()} value. - *
Only for {@link ChannelType#TEXT ChannelType.TEXT} + * Change of the {@link ISlowmodeChannel#getSlowmode()} value. * *

Expected type: Integer */ CHANNEL_SLOWMODE("rate_limit_per_user"), + /** + * Change of the {@link IThreadContainer#getDefaultThreadSlowmode()} value. + * + *

Expected type: Integer + */ + CHANNEL_DEFAULT_THREAD_SLOWMODE("default_thread_rate_limit_per_user"), + + /** + * Change of the {@link ForumChannel#getDefaultReaction()} value. + * + *

Expected type: Map containing {@code emoji_id} and {@code emoji_name} + */ + CHANNEL_DEFAULT_REACTION_EMOJI("default_reaction_emoji"), + /** * Change of the {@link VoiceChannel#getBitrate() VoiceChannel.getBitrate()} value. *
Only for {@link ChannelType#VOICE ChannelType.VOICE} @@ -277,6 +304,21 @@ public enum AuditLogKey */ CHANNEL_OVERRIDES("permission_overwrites"), + /** + * The available tags of this {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannel}. + * + *

Expected type: List{@literal >} + */ + CHANNEL_AVAILABLE_TAGS("available_tags"), + + /** + * The {@link ForumChannel#getDefaultSortOrder()} value. + *
Only for {@link ChannelType#FORUM}. + * + *

Expected type: Integer + */ + CHANNEL_DEFAULT_SORT_ORDER("default_sort_order"), + // THREADS /** @@ -287,10 +329,16 @@ public enum AuditLogKey THREAD_NAME("name"), /** - * Change of the {@link ThreadChannel#getSlowmode() ThreadChannel.getSlowmode()} value. + * Change of the {@link ISlowmodeChannel#getSlowmode()} value. * *

Expected type: Integer + * + * @deprecated Use {@link #CHANNEL_SLOWMODE} instead */ + @Deprecated + @ForRemoval + @DeprecatedSince("5.0.0") + @ReplaceWith("CHANNEL_SLOWMODE") THREAD_SLOWMODE("rate_limit_per_user"), /** @@ -322,6 +370,13 @@ public enum AuditLogKey */ THREAD_INVITABLE("invitable"), + /** + * The applied tags of this {@link ThreadChannel}, given that it is a forum post. + * + *

Expected type: List{@literal } + */ + THREAD_APPLIED_TAGS("applied_tags"), + // STAGE_INSTANCE /** diff --git a/src/main/java/net/dv8tion/jda/api/entities/Guild.java b/src/main/java/net/dv8tion/jda/api/entities/Guild.java index df7cf4aca9..0a447048f5 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java @@ -21,6 +21,7 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Region; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.attribute.ICopyableChannel; import net.dv8tion.jda.api.entities.channel.attribute.IGuildChannelContainer; import net.dv8tion.jda.api.entities.channel.attribute.IInviteContainer; @@ -1282,21 +1283,24 @@ default List getMembersWithRoles(@Nonnull Collection roles) @Override SortedSnowflakeCacheView getVoiceChannelCache(); + @Nonnull + @Override + SortedSnowflakeCacheView getForumChannelCache(); + /** * Populated list of {@link GuildChannel channels} for this guild. - * This includes all types of channels, such as category/voice/text. - *
This includes hidden channels by default. + *
This includes all types of channels, except for threads. + *
This includes hidden channels by default, + * you can use {@link #getChannels(boolean) getChannels(false)} to exclude hidden channels. * *

The returned list is ordered in the same fashion as it would be by the official discord client. *

    - *
  1. TextChannel and NewsChannel without parent
  2. - *
  3. VoiceChannel without parent
  4. - *
  5. StageChannel without parent
  6. + *
  7. TextChannel, ForumChannel, and NewsChannel without parent
  8. + *
  9. VoiceChannel and StageChannel without parent
  10. *
  11. Categories *
      - *
    1. TextChannel and NewsChannel with category as parent
    2. - *
    3. VoiceChannel with category as parent
    4. - *
    5. StageChannel with category as parent
    6. + *
    7. TextChannel, ForumChannel, and NewsChannel with category as parent
    8. + *
    9. VoiceChannel and StageChannel with category as parent
    10. *
    *
  12. *
@@ -1313,23 +1317,20 @@ default List getChannels() /** * Populated list of {@link GuildChannel channels} for this guild. - * This includes all types of channels, such as category/voice/text. + *
This includes all types of channels, except for threads. * *

The returned list is ordered in the same fashion as it would be by the official discord client. *

    - *
  1. TextChannel and NewsChannel without parent
  2. - *
  3. VoiceChannel without parent
  4. - *
  5. StageChannel without parent
  6. + *
  7. TextChannel, ForumChannel, and NewsChannel without parent
  8. + *
  9. VoiceChannel and StageChannel without parent
  10. *
  11. Categories *
      - *
    1. TextChannel and NewsChannel with category as parent
    2. - *
    3. VoiceChannel with category as parent
    4. - *
    5. StageChannel with category as parent
    6. + *
    7. TextChannel, ForumChannel, and NewsChannel with category as parent
    8. + *
    9. VoiceChannel and StageChannel with category as parent
    10. *
    *
  12. *
* - * * @param includeHidden * Whether to include channels with denied {@link Permission#VIEW_CHANNEL View Channel Permission} * @@ -3889,12 +3890,12 @@ default AuditableRestAction modifyMemberRoles(@Nonnull Member member, @Non * * * @param name - * The name of the TextChannel to create + * The name of the TextChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters * * @return A specific {@link net.dv8tion.jda.api.requests.restaction.ChannelAction ChannelAction} *
This action allows to set fields for the new TextChannel before creating it @@ -3921,14 +3922,14 @@ default ChannelAction createTextChannel(@Nonnull String name) * * * @param name - * The name of the TextChannel to create + * The name of the TextChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * @param parent * The optional parent category for this channel, or null * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length; + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters; * or the provided parent is not in the same guild. * * @return A specific {@link net.dv8tion.jda.api.requests.restaction.ChannelAction ChannelAction} @@ -3953,12 +3954,12 @@ default ChannelAction createTextChannel(@Nonnull String name) * * * @param name - * The name of the NewsChannel to create + * The name of the NewsChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters * * @return A specific {@link net.dv8tion.jda.api.requests.restaction.ChannelAction ChannelAction} *
This action allows to set fields for the new NewsChannel before creating it @@ -3985,14 +3986,14 @@ default ChannelAction createNewsChannel(@Nonnull String name) * * * @param name - * The name of the NewsChannel to create + * The name of the NewsChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * @param parent * The optional parent category for this channel, or null * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length; + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters; * or the provided parent is not in the same guild. * * @return A specific {@link net.dv8tion.jda.api.requests.restaction.ChannelAction ChannelAction} @@ -4017,12 +4018,12 @@ default ChannelAction createNewsChannel(@Nonnull String name) * * * @param name - * The name of the VoiceChannel to create + * The name of the VoiceChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters * * @return A specific {@link ChannelAction ChannelAction} *
This action allows to set fields for the new VoiceChannel before creating it @@ -4049,14 +4050,14 @@ default ChannelAction createVoiceChannel(@Nonnull String name) * * * @param name - * The name of the VoiceChannel to create + * The name of the VoiceChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * @param parent * The optional parent category for this channel, or null * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length; + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters; * or the provided parent is not in the same guild. * * @return A specific {@link ChannelAction ChannelAction} @@ -4081,12 +4082,12 @@ default ChannelAction createVoiceChannel(@Nonnull String name) * * * @param name - * The name of the StageChannel to create + * The name of the StageChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters * * @return A specific {@link ChannelAction ChannelAction} *
This action allows to set fields for the new StageChannel before creating it @@ -4113,14 +4114,14 @@ default ChannelAction createStageChannel(@Nonnull String name) * * * @param name - * The name of the StageChannel to create + * The name of the StageChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * @param parent * The optional parent category for this channel, or null * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length; + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters; * or the provided parent is not in the same guild. * * @return A specific {@link ChannelAction ChannelAction} @@ -4130,6 +4131,70 @@ default ChannelAction createStageChannel(@Nonnull String name) @CheckReturnValue ChannelAction createStageChannel(@Nonnull String name, @Nullable Category parent); + /** + * Creates a new {@link ForumChannel} in this Guild. + * For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission. + * + *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link RestAction RestAction} include the following: + *

    + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} + *
    The channel could not be created due to a permission discrepancy
  • + * + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS} + *
    The maximum number of channels were exceeded
  • + *
+ * + * @param name + * The name of the ForumChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) + * + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission + * @throws IllegalArgumentException + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters + * + * @return A specific {@link ChannelAction ChannelAction} + *
This action allows to set fields for the new ForumChannel before creating it + */ + @Nonnull + @CheckReturnValue + default ChannelAction createForumChannel(@Nonnull String name) + { + return createForumChannel(name, null); + } + + /** + * Creates a new {@link ForumChannel} in this Guild. + * For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission. + * + *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link RestAction RestAction} include the following: + *

    + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} + *
    The channel could not be created due to a permission discrepancy
  • + * + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS} + *
    The maximum number of channels were exceeded
  • + *
+ * + * @param name + * The name of the ForumChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) + * @param parent + * The optional parent category for this channel, or null + * + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission + * @throws IllegalArgumentException + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters; + * or the provided parent is not in the same guild. + * + * @return A specific {@link ChannelAction ChannelAction} + *
This action allows to set fields for the new ForumChannel before creating it + */ + @Nonnull + @CheckReturnValue + ChannelAction createForumChannel(@Nonnull String name, @Nullable Category parent); + /** * Creates a new {@link Category Category} in this Guild. * For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission. @@ -4145,12 +4210,12 @@ default ChannelAction createStageChannel(@Nonnull String name) * * * @param name - * The name of the Category to create + * The name of the Category to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters * * @return A specific {@link ChannelAction ChannelAction} *
This action allows to set fields for the new Category before creating it diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/Channel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/Channel.java index c281abae16..b3101cc2fa 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/Channel.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/Channel.java @@ -23,6 +23,7 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; +import java.util.EnumSet; import java.util.FormattableFlags; import java.util.Formatter; @@ -31,6 +32,23 @@ */ public interface Channel extends IMentionable { + /** + * The maximum length a channel name can be. ({@value #MAX_NAME_LENGTH}) + */ + int MAX_NAME_LENGTH = 100; + + /** + * The flags configured for this channel. + *
This feature is currently primarily used for {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannels}. + * + * @return {@link EnumSet} of the configured {@link ChannelFlag ChannelFlags}, changes to this enum set are not reflected in the API. + */ + @Nonnull + default EnumSet getFlags() + { + return EnumSet.noneOf(ChannelFlag.class); + } + /** * The human readable name of this channel. * @@ -71,6 +89,7 @@ public interface Channel extends IMentionable @CheckReturnValue RestAction delete(); + @Nonnull @Override default String getAsMention() { diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelField.java b/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelField.java index daba1753c7..f9e6ab031b 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelField.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelField.java @@ -18,6 +18,9 @@ import net.dv8tion.jda.api.audit.AuditLogKey; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.concrete.*; import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel; @@ -56,10 +59,17 @@ public enum ChannelField */ NAME("name", AuditLogKey.CHANNEL_NAME), + /** + * The flags of the channel. + * + * @see Channel#getFlags() + */ + FLAGS("flags", AuditLogKey.CHANNEL_FLAGS), + /** * The {@link Category parent} of the channel. * - * Limited to {@link net.dv8tion.jda.api.entities.channel.attribute.ICategorizableChannel Categorizable Channels} (and implementations). + *

Limited to {@link net.dv8tion.jda.api.entities.channel.attribute.ICategorizableChannel Categorizable Channels} (and implementations). * * @see net.dv8tion.jda.api.entities.channel.attribute.ICategorizableChannel#getParentCategory() */ @@ -74,13 +84,26 @@ public enum ChannelField */ POSITION("position", null), //Discord doesn't track Channel position changes in AuditLog. + /** + * The default slowmode applied to threads in a {@link net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer ThreadContainer}. + * + * @see IThreadContainer#getDefaultThreadSlowmode() + */ + DEFAULT_THREAD_SLOWMODE("default_thread_slowmode", AuditLogKey.CHANNEL_DEFAULT_THREAD_SLOWMODE), + + /** + * The default reaction emoji used in a {@link ForumChannel}. + * + * @see ForumChannel#getDefaultReaction() + */ + DEFAULT_REACTION_EMOJI("default_reaction_emoji", AuditLogKey.CHANNEL_DEFAULT_REACTION_EMOJI), //Text Specific /** * The topic of the channel. * - * Limited to {@link NewsChannel NewsChannels} and {@link TextChannel TextChannels}. + *

Limited to {@link NewsChannel NewsChannels} and {@link TextChannel TextChannels}. * * @see StandardGuildMessageChannel#getTopic() */ @@ -89,34 +112,42 @@ public enum ChannelField /** * The NSFW state of the channel. * - * Limited to {@link StandardGuildMessageChannel StandardGuildMessageChannels} (and implementations). + *

Limited to {@link IAgeRestrictedChannel IAgeRestrictedChannels} (and implementations). * - * @see StandardGuildMessageChannel#isNSFW() + * @see IAgeRestrictedChannel#isNSFW() */ NSFW("nsfw", AuditLogKey.CHANNEL_NSFW), /** - * The state of slow mode in the channel. This defines the minimum time between message sends. + * The state of slow mode in the channel. + *
This defines the minimum time between message sends. * - * Limited to {@link TextChannel Text Channels}. + *

Limited to {@link ISlowmodeChannel ISlowmodeChannels} (and implementations). * - * @see TextChannel#getSlowmode() + * @see ISlowmodeChannel#getSlowmode() */ SLOWMODE("slowmode", AuditLogKey.CHANNEL_SLOWMODE), + /** + * The applied tags of a {@link ForumChannel}. + * + * @see ForumChannel#getAvailableTags() + */ + AVAILABLE_TAGS("available_tags", AuditLogKey.CHANNEL_AVAILABLE_TAGS), + //Voice Specific /** * The bitrate (in bits per second) of the audio in this channel. * - * For standard channels this is between 8000 and 96000. + *

For standard channels this is between 8000 and 96000. * - * VIP servers extend this limit to 128000. + *

VIP servers extend this limit to 128000. *
* The bitrates of boost tiers may be found in {@link Guild.BoostTier the boost tiers}. * - * Limited to {@link AudioChannel Audio Channels}. + *

Limited to {@link AudioChannel Audio Channels}. * * @see AudioChannel#getBitrate() */ @@ -125,7 +156,7 @@ public enum ChannelField /** * The region of the channel. * - * Limited to {@link AudioChannel Audio Channels}. + *

Limited to {@link AudioChannel Audio Channels}. * * @see AudioChannel#getRegion() * @see net.dv8tion.jda.api.Region @@ -135,7 +166,7 @@ public enum ChannelField /** * The maximum user count of this channel. * - * Limited to {@link VoiceChannel Voice Channels}. + *

Limited to {@link VoiceChannel Voice Channels}. * * @see VoiceChannel#getUserLimit() */ @@ -147,9 +178,9 @@ public enum ChannelField /** * The auto archive duration of this channel. * - * If the thread is inactive for this long, it becomes auto-archived. + *

If the thread is inactive for this long, it becomes auto-archived. * - * Limited to {@link ThreadChannel Thread Channels}. + *

Limited to {@link ThreadChannel Thread Channels}. * * @see ThreadChannel#getAutoArchiveDuration() * @see ThreadChannel.AutoArchiveDuration @@ -159,9 +190,9 @@ public enum ChannelField /** * The archive state of this channel. * - * If the channel is archived, this is true. + *

If the channel is archived, this is true. * - * Limited to {@link ThreadChannel Thread Channels}. + *

Limited to {@link ThreadChannel Thread Channels}. * * @see ThreadChannel#isArchived() */ @@ -170,7 +201,7 @@ public enum ChannelField /** * The time this channel's archival information was last updated. * - * This timestamp will be updated when any of the following happen: + *

This timestamp will be updated when any of the following happen: *

    *
  • The channel is archived
  • *
  • The channel is unarchived
  • @@ -179,7 +210,6 @@ public enum ChannelField * * Limited to {@link ThreadChannel Thread Channels}. * - * * @see ThreadChannel#getTimeArchiveInfoLastModified() */ ARCHIVED_TIMESTAMP("archiveTimestamp", null), @@ -187,9 +217,9 @@ public enum ChannelField /** * The locked state of this channel. * - * If the channel is locked, this is true. + *

    If the channel is locked, this is true. * - * Limited to {@link ThreadChannel Thread Channels}. + *

    Limited to {@link ThreadChannel Thread Channels}. * * @see ThreadChannel#isLocked() */ @@ -198,14 +228,31 @@ public enum ChannelField /** * The invite state of this channel. * - * If the channel is invitable, this is true. + *

    If the channel is invitable, this is true. * - * Limited to {@link ThreadChannel Thread Channels}. + *

    Limited to {@link ThreadChannel Thread Channels}. * * @see ThreadChannel#isInvitable() */ - INVITABLE("invitable", AuditLogKey.THREAD_INVITABLE) - ; + INVITABLE("invitable", AuditLogKey.THREAD_INVITABLE), + + /** + * The tags applied to a forum post thread. + * + *

    Limited to {@link ThreadChannel ThreadChannels} inside {@link ForumChannel ForumChannels} + * + * @see ThreadChannel#getAppliedTags() + */ + APPLIED_TAGS("applied_tags", AuditLogKey.THREAD_APPLIED_TAGS), + + /** + * The default sort order of a forum channel. + * + *

    Limited to {@link ForumChannel Forum Channels}. + * + * @see ForumChannel#getDefaultSortOrder() + */ + DEFAULT_SORT_ORDER("default_sort_order", AuditLogKey.CHANNEL_DEFAULT_SORT_ORDER); private final String fieldName; private final AuditLogKey auditLogKey; @@ -228,6 +275,8 @@ public AuditLogKey getAuditLogKey() return auditLogKey; } + @Nonnull + @Override public String toString() { return "ChannelField." + name() + '(' + fieldName + ')'; diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelFlag.java b/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelFlag.java new file mode 100644 index 0000000000..a807d7174e --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelFlag.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.api.entities.channel; + +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.internal.utils.Checks; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.EnumSet; + +/** + * Flags for specific channel settings. + */ +public enum ChannelFlag +{ + /** + * This is a forum post {@link ThreadChannel} which is pinned in the {@link ForumChannel}. + */ + PINNED(1 << 1), + /** + * This is a {@link ForumChannel} which requires all new post threads to have at least one applied tag. + */ + REQUIRE_TAG(1 << 4); + + private final int value; + + ChannelFlag(int value) + { + this.value = value; + } + + /** + * The raw bitset value of this flag. + * + * @return The raw value + */ + public int getRaw() + { + return value; + } + + /** + * Parses the provided bitset to the corresponding enum constants. + * + * @param bitset + * The bitset of channel flags + * + * @return The enum constants of the provided bitset + */ + @Nonnull + public static EnumSet fromRaw(int bitset) + { + EnumSet set = EnumSet.noneOf(ChannelFlag.class); + if (bitset == 0) + return set; + + for (ChannelFlag flag : values()) + { + if (flag.value == bitset) + set.add(flag); + } + + return set; + } + + /** + * The raw bitset value for the provided flags. + * + * @return The raw value + */ + public static int getRaw(@Nonnull Collection flags) + { + Checks.notNull(flags, "Flags"); + int raw = 0; + for (ChannelFlag flag : flags) + raw |= flag.getRaw(); + return raw; + } +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelType.java b/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelType.java index 1b932a1d8c..7fce6cdcc1 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelType.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelType.java @@ -58,6 +58,11 @@ public enum ChannelType GUILD_PUBLIC_THREAD(11, -1, true), GUILD_PRIVATE_THREAD(12, -1, true), + /** + * A {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannel}, Guild-Only. + */ + FORUM(15, 0, true), + /** * Unknown Discord channel type. Should never happen and would only possibly happen if Discord implemented a new * channel type and JDA had yet to implement support for it. diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IGuildChannelContainer.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IGuildChannelContainer.java index 82e7061985..25de06aef9 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IGuildChannelContainer.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IGuildChannelContainer.java @@ -153,6 +153,7 @@ default GuildChannel getGuildChannelById(@Nonnull String id) *

  • {@link #getStageChannelById(long)}
  • *
  • {@link #getVoiceChannelById(long)}
  • *
  • {@link #getCategoryById(long)}
  • + *
  • {@link #getForumChannelById(long)}
  • *
* * @param id @@ -175,6 +176,8 @@ default GuildChannel getGuildChannelById(long id) channel = getCategoryById(id); if (channel == null) channel = getThreadChannelById(id); + if (channel == null) + channel = getForumChannelById(id); return channel; } @@ -200,6 +203,7 @@ default GuildChannel getGuildChannelById(long id) *
  • {@link #getStageChannelById(String)}
  • *
  • {@link #getVoiceChannelById(String)}
  • *
  • {@link #getCategoryById(String)}
  • + *
  • {@link #getForumChannelById(String)}
  • * * * @param type @@ -241,6 +245,7 @@ default GuildChannel getGuildChannelById(@Nonnull ChannelType type, @Nonnull Str *
  • {@link #getStageChannelById(long)}
  • *
  • {@link #getVoiceChannelById(long)}
  • *
  • {@link #getCategoryById(long)}
  • + *
  • {@link #getForumChannelById(long)}
  • * * * @param type @@ -266,6 +271,8 @@ default GuildChannel getGuildChannelById(@Nonnull ChannelType type, long id) return getStageChannelById(id); case CATEGORY: return getCategoryById(id); + case FORUM: + return getForumChannelById(id); } if (type.isThread()) @@ -970,4 +977,118 @@ default List getVoiceChannels() { return getVoiceChannelCache().asList(); } + + + // ForumChannels + + + /** + * {@link SnowflakeCacheView SnowflakeCacheView} of {@link ForumChannel}. + * + *

    This getter exists on any instance of {@link IGuildChannelContainer} and only checks the caches with the relevant scoping. + * For {@link Guild}, {@link JDA}, or {@link ShardManager}, + * this returns the relevant channel with respect to the cache within each of those objects. + * For a guild, this would mean it only returns channels within the same guild. + *
    If this is called on {@link JDA} or {@link ShardManager}, this may return null immediately after building, because the cache isn't initialized yet. + * To make sure the cache is initialized after building your {@link JDA} instance, you can use {@link JDA#awaitReady()}. + * + * @return {@link SnowflakeCacheView SnowflakeCacheView} + */ + @Nonnull + SnowflakeCacheView getForumChannelCache(); + + /** + * Gets a list of all {@link ForumChannel ForumChannels} + * in this Guild that have the same name as the one provided. + *
    If there are no channels with the provided name, then this returns an empty list. + * + *

    This getter exists on any instance of {@link IGuildChannelContainer} and only checks the caches with the relevant scoping. + * For {@link Guild}, {@link JDA}, or {@link ShardManager}, + * this returns the relevant channel with respect to the cache within each of those objects. + * For a guild, this would mean it only returns channels within the same guild. + *
    If this is called on {@link JDA} or {@link ShardManager}, this may return null immediately after building, because the cache isn't initialized yet. + * To make sure the cache is initialized after building your {@link JDA} instance, you can use {@link JDA#awaitReady()}. + * + * @param name + * The name used to filter the returned {@link ForumChannel ForumChannels}. + * @param ignoreCase + * Determines if the comparison ignores case when comparing. True - case insensitive. + * + * @return Possibly-empty immutable list of all ForumChannel names that match the provided name. + */ + @Nonnull + default List getForumChannelsByName(@Nonnull String name, boolean ignoreCase) + { + return getForumChannelCache().getElementsByName(name, ignoreCase); + } + + /** + * Gets a {@link ForumChannel} that has the same id as the one provided. + *
    If there is no channel with an id that matches the provided one, then this returns {@code null}. + * + *

    This getter exists on any instance of {@link IGuildChannelContainer} and only checks the caches with the relevant scoping. + * For {@link Guild}, {@link JDA}, or {@link ShardManager}, + * this returns the relevant channel with respect to the cache within each of those objects. + * For a guild, this would mean it only returns channels within the same guild. + *
    If this is called on {@link JDA} or {@link ShardManager}, this may return null immediately after building, because the cache isn't initialized yet. + * To make sure the cache is initialized after building your {@link JDA} instance, you can use {@link JDA#awaitReady()}. + * + * @param id + * The id of the {@link ForumChannel}. + * + * @throws java.lang.NumberFormatException + * If the provided {@code id} cannot be parsed by {@link Long#parseLong(String)} + * + * @return Possibly-null {@link ForumChannel} with matching id. + */ + @Nullable + default ForumChannel getForumChannelById(@Nonnull String id) + { + return getForumChannelCache().getElementById(id); + } + + /** + * Gets a {@link ForumChannel} that has the same id as the one provided. + *
    If there is no channel with an id that matches the provided one, then this returns {@code null}. + * + *

    This getter exists on any instance of {@link IGuildChannelContainer} and only checks the caches with the relevant scoping. + * For {@link Guild}, {@link JDA}, or {@link ShardManager}, + * this returns the relevant channel with respect to the cache within each of those objects. + * For a guild, this would mean it only returns channels within the same guild. + *
    If this is called on {@link JDA} or {@link ShardManager}, this may return null immediately after building, because the cache isn't initialized yet. + * To make sure the cache is initialized after building your {@link JDA} instance, you can use {@link JDA#awaitReady()}. + * + * @param id + * The id of the {@link ForumChannel}. + * + * @return Possibly-null {@link ForumChannel} with matching id. + */ + @Nullable + default ForumChannel getForumChannelById(long id) + { + return getForumChannelCache().getElementById(id); + } + + /** + * Gets all {@link ForumChannel} in the cache. + * + *

    This copies the backing store into a list. This means every call + * creates a new list with O(n) complexity. It is recommended to store this into + * a local variable or use {@link #getForumChannelCache()} and use its more efficient + * versions of handling these values. + * + *

    This getter exists on any instance of {@link IGuildChannelContainer} and only checks the caches with the relevant scoping. + * For {@link Guild}, {@link JDA}, or {@link ShardManager}, + * this returns the relevant channel with respect to the cache within each of those objects. + * For a guild, this would mean it only returns channels within the same guild. + *
    If this is called on {@link JDA} or {@link ShardManager}, this may return null immediately after building, because the cache isn't initialized yet. + * To make sure the cache is initialized after building your {@link JDA} instance, you can use {@link JDA#awaitReady()}. + * + * @return An immutable List of {@link ForumChannel}. + */ + @Nonnull + default List getForumChannels() + { + return getForumChannelCache().asList(); + } } diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/ISlowmodeChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/ISlowmodeChannel.java new file mode 100644 index 0000000000..25d9248284 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/ISlowmodeChannel.java @@ -0,0 +1,48 @@ +/* + * 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.api.entities.channel.attribute; + +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; + +/** + * Channels which support slowmode. + */ +public interface ISlowmodeChannel extends GuildChannel +{ + /** + * The maximum duration of slowmode in seconds + */ + int MAX_SLOWMODE = 21600; + + /** + * The slowmode set for this channel. + *
    If slowmode is set, this returns an {@code int} between 1 and {@value #MAX_SLOWMODE}. + *
    Otherwise, if no slowmode is set, this returns {@code 0}. + * + *

    Note bots are unaffected by this. + *
    Having {@link net.dv8tion.jda.api.Permission#MESSAGE_MANAGE MESSAGE_MANAGE} or + * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} permission also + * grants immunity to slowmode. + * + *

    Special case
    + * {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannels} use this to limit how many posts a user can create. + * The client refers to this as the post slowmode. + * + * @return The slowmode for this channel, between 1 and {@value #MAX_SLOWMODE}, or {@code 0} if no slowmode is set. + */ + int getSlowmode(); +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IThreadContainer.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IThreadContainer.java index 07eaa4013c..98003a8a94 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IThreadContainer.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IThreadContainer.java @@ -16,6 +16,8 @@ package net.dv8tion.jda.api.entities.channel.attribute; +import net.dv8tion.jda.api.entities.MessageType; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; @@ -23,6 +25,7 @@ import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction; import net.dv8tion.jda.api.requests.restaction.pagination.ThreadChannelPaginationAction; import net.dv8tion.jda.api.utils.MiscUtil; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; @@ -30,29 +33,40 @@ import java.util.List; import java.util.stream.Collectors; +/** + * Abstraction of all channel types, which can contain or manage {@link ThreadChannel ThreadChannels}. + * + * @see ThreadChannel#getParentChannel() + * @see net.dv8tion.jda.api.entities.channel.unions.IThreadContainerUnion IThreadContainerUnion + */ public interface IThreadContainer extends GuildChannel, IPermissionContainer { + /** + * The default {@link ISlowmodeChannel#getSlowmode() slowmode} for thread channels that is copied on thread creation. + *
    Users have to wait this amount of seconds before sending another message to the same thread. + * + * @return The default slowmode seconds for new threads, or {@code 0} if unset + */ + int getDefaultThreadSlowmode(); + /** * Finds all {@link ThreadChannel ThreadChannels} whose parent is this channel. * - * @return a list of all ThreadChannel children. + * @return Immutable list of all ThreadChannel children. */ default List getThreadChannels() { return Collections.unmodifiableList( - getGuild().getThreadChannels() - .stream() - .filter(thread -> thread.getParentChannel() == this) - .collect(Collectors.toList()) - ); + getGuild().getThreadChannelCache().applyStream(stream -> + stream.filter(thread -> thread.getParentChannel() == this) + .collect(Collectors.toList()) + )); } - /** - * Creates a new, public {@link ThreadChannel} with the parent channel being this {@link IThreadContainer}. - * This requires the bot to have the {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL} and {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS} permissions. + * Creates a new public {@link ThreadChannel} with the parent channel being this {@link IThreadContainer}. * - * The resulting {@link ThreadChannel ThreadChannel} may be either one of: + *

    The resulting {@link ThreadChannel ThreadChannel} may be either one of: *

      *
    • {@link ChannelType#GUILD_PUBLIC_THREAD}
    • *
    • {@link ChannelType#GUILD_NEWS_THREAD}
    • @@ -69,26 +83,35 @@ default List getThreadChannels() * *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_ACTIVE_THREADS} *
      The maximum number of active threads has been reached, and no more may be created.
    • - * *
    * - * @param name - * The name of the new ThreadChannel + * @param name + * The name of the new ThreadChannel (up to {@value Channel#MAX_NAME_LENGTH} characters) + * + * @throws IllegalArgumentException + * If the provided name is null, blank, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters + * @throws UnsupportedOperationException + * If this is a forum channel. + * You must use {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel#createForumPost(String, MessageCreateData) createForumPost(...)} instead. + * @throws InsufficientPermissionException + *
      + *
    • If the bot does not have {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL}
    • + *
    • If the bot does not have {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS Permission.CREATE_PUBLIC_THREADS}
    • + *
    * * @return A specific {@link ThreadChannelAction} that may be used to configure the new ThreadChannel before its creation. */ @Nonnull @CheckReturnValue - default ThreadChannelAction createThreadChannel(String name) + default ThreadChannelAction createThreadChannel(@Nonnull String name) { return createThreadChannel(name, false); } /** * Creates a new {@link ThreadChannel} with the parent channel being this {@link IThreadContainer}. - * This requires the bot to have the {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL} and {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS} permissions. * - * The resulting {@link ThreadChannel ThreadChannel} may be one of: + *

    The resulting {@link ThreadChannel ThreadChannel} may be one of: *

      *
    • {@link ChannelType#GUILD_PUBLIC_THREAD}
    • *
    • {@link ChannelType#GUILD_NEWS_THREAD}
    • @@ -109,30 +132,36 @@ default ThreadChannelAction createThreadChannel(String name) * *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS} *
      Due to missing private thread permissions.
    • - * *
    * * @param name - * The name of the new ThreadChannel + * The name of the new ThreadChannel (up to {@value Channel#MAX_NAME_LENGTH} characters) * @param isPrivate * The public/private status of the new ThreadChannel. If true, the new ThreadChannel will be private. * + * @throws IllegalArgumentException + * If the provided name is null, blank, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters + * @throws UnsupportedOperationException + * If this is a forum channel. + * You must use {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel#createForumPost(String, MessageCreateData) createForumPost(...)} instead. * @throws InsufficientPermissionException - * if the ThreadChannel is set to private, and the logged in account does not have {@link net.dv8tion.jda.api.Permission#CREATE_PRIVATE_THREADS}. + *
      + *
    • If the bot does not have {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL}
    • + *
    • If the thread is {@code private}, and the bot does not have {@link net.dv8tion.jda.api.Permission#CREATE_PRIVATE_THREADS Permission.CREATE_PRIVATE_THREADS}
    • + *
    • If the thread is not {@code private}, and the bot does not have {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS Permission.CREATE_PUBLIC_THREADS}
    • + *
    * * @return A specific {@link ThreadChannelAction} that may be used to configure the new ThreadChannel before its creation. */ @Nonnull @CheckReturnValue - ThreadChannelAction createThreadChannel(String name, boolean isPrivate); - + ThreadChannelAction createThreadChannel(@Nonnull String name, boolean isPrivate); /** * Creates a new, public {@link ThreadChannel} with the parent channel being this {@link IThreadContainer}. - * This ThreadChannel will be spawned from the given messageID, and will consequently share its ID with the message. - * This requires the bot to have {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL} and {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS} permissions. + *
    The starting message will copy the message for the provided id, and will be of type {@link MessageType#THREAD_STARTER_MESSAGE MessageType.THREAD_STARTER_MESSAGE}. * - * The resulting {@link ThreadChannel ThreadChannel} may be one of: + *

    The resulting {@link ThreadChannel ThreadChannel} may be one of: *

      *
    • {@link ChannelType#GUILD_PUBLIC_THREAD}
    • *
    • {@link ChannelType#GUILD_NEWS_THREAD}
    • @@ -152,27 +181,33 @@ default ThreadChannelAction createThreadChannel(String name) * *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_ACTIVE_THREADS} *
      The maximum number of active threads has been reached, and no more may be created.
    • - * *
    * - * @param name - * The name of the new ThreadChannel - * @param messageId - * The ID of the message from which this ThreadChannel will be spawned. + * @param name + * The name of the new ThreadChannel (up to {@value Channel#MAX_NAME_LENGTH} characters) + * @param messageId + * The ID of the message from which this ThreadChannel will be spawned. + * + * @throws IllegalArgumentException + * If the provided name is null, blank, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters + * @throws UnsupportedOperationException + * If this is a forum channel. + * You must use {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel#createForumPost(String, MessageCreateData) createForumPost(...)} instead. + * @throws InsufficientPermissionException + * If the bot does not have {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS Permission.CREATE_PUBLIC_THREADS} in this channel * * @return A specific {@link ThreadChannelAction} that may be used to configure the new ThreadChannel before its creation. */ @Nonnull @CheckReturnValue - ThreadChannelAction createThreadChannel(String name, long messageId); + ThreadChannelAction createThreadChannel(@Nonnull String name, long messageId); /** * Creates a new, public {@link ThreadChannel} with the parent channel being this {@link IThreadContainer}. - * This ThreadChannel will be spawned from the given messageID, and will consequently share its ID with the message. - * This requires the bot to have {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL} and {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS} permissions. + *
    The starting message will copy the message for the provided id, and will be of type {@link MessageType#THREAD_STARTER_MESSAGE MessageType.THREAD_STARTER_MESSAGE}. * - * The resulting {@link ThreadChannel ThreadChannel} may be one of: + *

    The resulting {@link ThreadChannel ThreadChannel} may be one of: *

      *
    • {@link ChannelType#GUILD_PUBLIC_THREAD}
    • *
    • {@link ChannelType#GUILD_NEWS_THREAD}
    • @@ -192,34 +227,78 @@ default ThreadChannelAction createThreadChannel(String name) * *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_ACTIVE_THREADS} *
      The maximum number of active threads has been reached, and no more may be created.
    • - * *
    * - * @param name - * The name of the new ThreadChannel - * @param messageId - * The ID of the message from which this ThreadChannel will be spawned. + * @param name + * The name of the new ThreadChannel (up to {@value Channel#MAX_NAME_LENGTH} characters) + * @param messageId + * The ID of the message from which this ThreadChannel will be spawned. + * + * @throws IllegalArgumentException + * If the provided name is null, blank, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters. + * Or the message id is not a valid snowflake. + * @throws UnsupportedOperationException + * If this is a forum channel. + * You must use {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel#createForumPost(String, MessageCreateData) createForumPost(...)} instead. + * @throws InsufficientPermissionException + * If the bot does not have {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS Permission.CREATE_PUBLIC_THREADS} in this channel * * @return A specific {@link ThreadChannelAction} that may be used to configure the new ThreadChannel before its creation. */ @Nonnull @CheckReturnValue - default ThreadChannelAction createThreadChannel(String name, String messageId) + default ThreadChannelAction createThreadChannel(@Nonnull String name, @Nonnull String messageId) { return createThreadChannel(name, MiscUtil.parseSnowflake(messageId)); } - //TODO-v5: Docs + /** + * Retrieves the archived public {@link ThreadChannel ThreadChannels} for this channel. + *
    This will iterate over all previously opened public threads, that have been archived. + * + *

    You can use {@link #retrieveArchivedPrivateThreadChannels()}, to get all private archived threads. + * + * @throws InsufficientPermissionException + * If the bot does not have {@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY Permission.MESSAGE_HISTORY} in this channel + * + * @return {@link ThreadChannelPaginationAction} to iterate over all public archived ThreadChannels + */ @Nonnull @CheckReturnValue ThreadChannelPaginationAction retrieveArchivedPublicThreadChannels(); - //TODO-v5: Docs + /** + * Retrieves the archived private {@link ThreadChannel ThreadChannels} for this channel. + *
    This will iterate over all previously opened private threads, that have been archived. + * This is a moderator restricted method, since private threads are only visible to members with {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS Permission.MANAGE_THREADS}. + * + *

    You can use {@link #retrieveArchivedPublicThreadChannels()}, to get all public archived threads. + * + *

    Note that {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannels} cannot have private threads. + * + * @throws InsufficientPermissionException + * If the bot does not have {@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY Permission.MESSAGE_HISTORY} + * or {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS Permission.MANAGE_THREADS} in this channel + * + * @return {@link ThreadChannelPaginationAction} to iterate over all private archived ThreadChannels + */ @Nonnull @CheckReturnValue ThreadChannelPaginationAction retrieveArchivedPrivateThreadChannels(); - //TODO-v5: Docs + /** + * Retrieves the archived private {@link ThreadChannel ThreadChannels} for this channel, that the bot has previously joined or been added to. + *
    Unlike {@link #retrieveArchivedPrivateThreadChannels()}, this only checks for threads which the bot has joined, and thus does not require permissions to manage threads. + * + *

    You can use {@link #retrieveArchivedPrivateThreadChannels()}, to get all private archived threads. + * + *

    Note that {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannels} cannot have private threads. + * + * @throws InsufficientPermissionException + * If the bot does not have {@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY Permission.MESSAGE_HISTORY} in this channel + * + * @return {@link ThreadChannelPaginationAction} to iterate over all joined private archived ThreadChannels + */ @Nonnull @CheckReturnValue ThreadChannelPaginationAction retrieveArchivedPrivateJoinedThreadChannels(); diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/Category.java b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/Category.java index d42a9112d3..1a9e78ce21 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/Category.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/Category.java @@ -20,6 +20,7 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.IPermissionHolder; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.attribute.ICopyableChannel; import net.dv8tion.jda.api.entities.channel.attribute.IMemberContainer; import net.dv8tion.jda.api.entities.channel.attribute.IPermissionContainer; @@ -57,10 +58,8 @@ public interface Category extends GuildChannel, ICopyableChannel, IPositionableChannel, IPermissionContainer, IMemberContainer { /** - * All {@link GuildChannel Channels} listed - * for this Category - *
    This may contain {@link VoiceChannel VoiceChannels}, - * and {@link TextChannel TextChannels}! + * All {@link GuildChannel Channels} listed for this Category. + *
    Includes all types of channels, except for threads. * * @return Immutable list of all child channels */ @@ -72,6 +71,7 @@ default List getChannels() channels.addAll(getVoiceChannels()); channels.addAll(getStageChannels()); channels.addAll(getNewsChannels()); + channels.addAll(getForumChannels()); Collections.sort(channels); return Collections.unmodifiableList(channels); @@ -86,9 +86,11 @@ default List getChannels() @Nonnull default List getTextChannels() { - return Collections.unmodifiableList(getGuild().getTextChannelCache().stream() - .filter(channel -> equals(channel.getParentCategory())) - .sorted().collect(Collectors.toList())); + return Collections.unmodifiableList(getGuild().getTextChannelCache().applyStream(stream -> + stream.filter(channel -> equals(channel.getParentCategory())) + .sorted() + .collect(Collectors.toList()) + )); } /** @@ -100,9 +102,26 @@ default List getTextChannels() @Nonnull default List getNewsChannels() { - return Collections.unmodifiableList(getGuild().getNewsChannelCache().stream() - .filter(channel -> equals(channel.getParentCategory())) - .sorted().collect(Collectors.toList())); + return Collections.unmodifiableList(getGuild().getNewsChannelCache().applyStream(stream -> + stream.filter(channel -> equals(channel.getParentCategory())) + .sorted() + .collect(Collectors.toList()) + )); + } + + /** + * All {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannels} listed for this Category + * + * @return Immutable list of all child ForumChannels + */ + @Nonnull + default List getForumChannels() + { + return Collections.unmodifiableList(getGuild().getForumChannelCache().applyStream(stream -> + stream.filter(channel -> equals(channel.getParentCategory())) + .sorted() + .collect(Collectors.toList()) + )); } /** @@ -114,9 +133,11 @@ default List getNewsChannels() @Nonnull default List getVoiceChannels() { - return Collections.unmodifiableList(getGuild().getVoiceChannelCache().stream() - .filter(channel -> equals(channel.getParentCategory())) - .sorted().collect(Collectors.toList())); + return Collections.unmodifiableList(getGuild().getVoiceChannelCache().applyStream(stream -> + stream.filter(channel -> equals(channel.getParentCategory())) + .sorted() + .collect(Collectors.toList()) + )); } /** @@ -128,9 +149,11 @@ default List getVoiceChannels() @Nonnull default List getStageChannels() { - return Collections.unmodifiableList(getGuild().getStageChannelCache().stream() - .filter(channel -> equals(channel.getParentCategory())) - .sorted().collect(Collectors.toList())); + return Collections.unmodifiableList(getGuild().getStageChannelCache().applyStream(stream -> + stream.filter(channel -> equals(channel.getParentCategory())) + .sorted() + .collect(Collectors.toList()) + )); } /** @@ -156,12 +179,12 @@ default List getStageChannels() * * * @param name - * The name of the TextChannel to create + * The name of the TextChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length + * If the provided name is {@code null}, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters * * @return A specific {@link ChannelAction ChannelAction} *
    This action allows to set fields for the new TextChannel before creating it @@ -170,6 +193,43 @@ default List getStageChannels() @CheckReturnValue ChannelAction createTextChannel(@Nonnull String name); + /** + * Creates a new {@link net.dv8tion.jda.api.entities.channel.concrete.NewsChannel NewsChannel} with this Category as parent. + * For this to be successful, the logged in account has to have the + * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission in this Category. + * + *

    This will copy all {@link net.dv8tion.jda.api.entities.PermissionOverride PermissionOverrides} of this Category! + * Unless the bot is unable to sync it with this category due to permission escalation. + * See {@link IPermissionHolder#canSync(IPermissionContainer, IPermissionContainer)} for details. + * + *

    Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following: + *

      + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} + *
      The channel could not be created due to a permission discrepancy
    • + * + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_ACCESS MISSING_ACCESS} + *
      The {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL VIEW_CHANNEL} permission was removed
    • + * + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS} + *
      The maximum number of channels were exceeded
    • + *
    + * + * @param name + * The name of the NewsChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) + * + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission + * @throws IllegalArgumentException + * If the provided name is {@code null}, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters + * + * @return A specific {@link ChannelAction ChannelAction} + *
    This action allows to set fields for the new NewsChannel before creating it + */ + @Nonnull + @CheckReturnValue + ChannelAction createNewsChannel(@Nonnull String name); + /** * Creates a new {@link VoiceChannel VoiceChannel} with this Category as parent. * For this to be successful, the logged in account has to have the @@ -193,12 +253,12 @@ default List getStageChannels() * * * @param name - * The name of the VoiceChannel to create + * The name of the VoiceChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length + * If the provided name is {@code null}, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters * * @return A specific {@link ChannelAction ChannelAction} *
    This action allows to set fields for the new VoiceChannel before creating it @@ -230,12 +290,12 @@ default List getStageChannels() * * * @param name - * The name of the StageChannel to create + * The name of the StageChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission * @throws IllegalArgumentException - * If the provided name is {@code null} or empty or greater than 100 characters in length + * If the provided name is {@code null}, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters * * @return A specific {@link ChannelAction ChannelAction} *
    This action allows to set fields for the new StageChannel before creating it @@ -244,6 +304,43 @@ default List getStageChannels() @CheckReturnValue ChannelAction createStageChannel(@Nonnull String name); + /** + * Creates a new {@link ForumChannel} with this Category as parent. + * For this to be successful, the logged in account has to have the + * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission in this Category. + * + *

    This will copy all {@link net.dv8tion.jda.api.entities.PermissionOverride PermissionOverrides} of this Category! + * Unless the bot is unable to sync it with this category due to permission escalation. + * See {@link IPermissionHolder#canSync(IPermissionContainer, IPermissionContainer)} for details. + * + *

    Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following: + *

      + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} + *
      The channel could not be created due to a permission discrepancy
    • + * + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_ACCESS MISSING_ACCESS} + *
      The {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL VIEW_CHANNEL} permission was removed
    • + * + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS} + *
      The maximum number of channels were exceeded
    • + *
    + * + * @param name + * The name of the ForumChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) + * + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission + * @throws IllegalArgumentException + * If the provided name is {@code null}, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters + * + * @return A specific {@link ChannelAction ChannelAction} + *
    This action allows to set fields for the new ForumChannel before creating it + */ + @Nonnull + @CheckReturnValue + ChannelAction createForumChannel(@Nonnull String name); + /** * Modifies the positional order of this Category's nested {@link #getTextChannels() TextChannels} and {@link #getNewsChannels() NewsChannels}. *
    This uses an extension of {@link ChannelOrderAction ChannelOrderAction} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/ForumChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/ForumChannel.java new file mode 100644 index 0000000000..ff3e7f3656 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/ForumChannel.java @@ -0,0 +1,302 @@ +/* + * 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.api.entities.channel.concrete; + +import net.dv8tion.jda.annotations.Incubating; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.ChannelFlag; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; +import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; +import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildChannel; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.managers.channel.concrete.ForumChannelManager; +import net.dv8tion.jda.api.requests.restaction.ChannelAction; +import net.dv8tion.jda.api.requests.restaction.ForumPostAction; +import net.dv8tion.jda.api.utils.cache.SortedSnowflakeCacheView; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; + +/** + * A Forum Channel which contains {@link #createForumPost(String, MessageCreateData) Forum Posts}. + *
    Forum posts are simply {@link ThreadChannel ThreadChannels} of type {@link ChannelType#GUILD_PUBLIC_THREAD}. + * + *

    The {@code CREATE POSTS} permission that is shown in the official Discord Client, is an alias for {@link net.dv8tion.jda.api.Permission#MESSAGE_SEND Permission.MESSAGE_SEND}. + * {@link net.dv8tion.jda.api.Permission#CREATE_PUBLIC_THREADS Permission.CREATE_PUBLIC_THREADS} is ignored for creating forum posts. + * + * @see Guild#createForumChannel(String, Category) + * @see #createForumPost(String, MessageCreateData) + */ +public interface ForumChannel extends StandardGuildChannel, IThreadContainer, IWebhookContainer, IAgeRestrictedChannel, ISlowmodeChannel +{ + /** + * The maximum length of a forum topic ({@value #MAX_FORUM_TOPIC_LENGTH}) + */ + int MAX_FORUM_TOPIC_LENGTH = 4096; + /** + * The maximum number of {@link ForumPostAction#setTags(Collection) tags} that can be applied to a forum post. ({@value #MAX_POST_TAGS}) + */ + int MAX_POST_TAGS = 5; + + @Nonnull + @Override + default ChannelType getType() + { + return ChannelType.FORUM; + } + + @Nonnull + @Override + ForumChannelManager getManager(); + + @Nonnull + @Override + ChannelAction createCopy(@Nonnull Guild guild); + + @Nonnull + @Override + default ChannelAction createCopy() + { + return createCopy(getGuild()); + } + + /** + * The available {@link ForumTag ForumTags} for this forum channel. + *
    Tags are sorted by their {@link ForumTag#getPosition() position} ascending. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @return {@link SortedSnowflakeCacheView} of {@link ForumTag} + */ + @Nonnull + SortedSnowflakeCacheView getAvailableTagCache(); + + /** + * The available {@link ForumTag ForumTags} for this forum channel. + *
    Tags are sorted by their {@link ForumTag#getPosition() position} ascending. + * + *

    This is a shortcut for {@link #getAvailableTagCache() getAvailableTagCache().asList()}. + * This method will copy the underlying cache into the list, running in {@code O(n)} time. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @return Immutable {@link List} of {@link ForumTag} + */ + @Nonnull + default List getAvailableTags() + { + return getAvailableTagCache().asList(); + } + + /** + * The available {@link ForumTag ForumTags} for this forum channel. + *
    Tags are sorted by their {@link ForumTag#getPosition() position} ascending. + * + *

    This is a shortcut for {@link #getAvailableTagCache() getAvailableTagCache().getElementsByName(name, ignoreCase)}. + * This method will copy the underlying cache into the list, running in {@code O(n)} time. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @param name + * The name of the tag + * @param ignoreCase + * Whether to use {@link String#equalsIgnoreCase(String)} + * + * @throws IllegalArgumentException + * If the name is {@code null} + * + * @return Immutable {@link List} of {@link ForumTag} with the given name + */ + @Nonnull + default List getAvailableTagsByName(@Nonnull String name, boolean ignoreCase) + { + return getAvailableTagCache().getElementsByName(name, ignoreCase); + } + + /** + * Retrieves the tag for the provided id. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @param id + * The tag id + * + * @return The tag for the provided id, or {@code null} if no tag with that id exists + * + * @see net.dv8tion.jda.api.entities.channel.forums.ForumTagSnowflake#fromId(long) + */ + @Nullable + default ForumTag getAvailableTagById(long id) + { + return getAvailableTagCache().getElementById(id); + } + + /** + * Retrieves the tag for the provided id. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @param id + * The tag id + * + * @throws IllegalArgumentException + * If the provided id is null + * @throws NumberFormatException + * If the provided id is not a valid snowflake + * + * @return The tag for the provided id, or {@code null} if no tag with that id exists + * + * @see net.dv8tion.jda.api.entities.channel.forums.ForumTagSnowflake#fromId(String) + */ + @Nullable + default ForumTag getAvailableTagById(@Nonnull String id) + { + return getAvailableTagCache().getElementById(id); + } + + /** + * The topic set for this channel, this is referred to as Guidelines in the official Discord client. + *
    If no topic has been set, this returns null. + * + * @return Possibly-null String containing the topic of this channel. + */ + @Nullable + String getTopic(); + + /** + * Whether all new forum posts must have a tag. + * + * @return True, if all new posts must have a tag. + */ + default boolean isTagRequired() + { + return getFlags().contains(ChannelFlag.REQUIRE_TAG); + } + + /** + * The emoji which will show up on new forum posts as default reaction. + * + * @return The default reaction for new forum posts. + */ + @Nullable + EmojiUnion getDefaultReaction(); + + /** + * The default order used to show threads. + * + * @return The default order used to show threads. + */ + @Nonnull + SortOrder getDefaultSortOrder(); + + /** + * Creates a new forum post (thread) in this forum. + * + *

    Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include: + *

      + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL} + *
      If the forum channel was deleted
    • + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#REQUEST_ENTITY_TOO_LARGE REQUEST_ENTITY_TOO_LARGE} + *
      If the total sum of uploaded bytes exceeds the guild's {@link Guild#getMaxFileSize() upload limit}
    • + *
    + * + * @param name + * The name of the post (up to {@value Channel#MAX_NAME_LENGTH} characters) + * @param message + * The starting message of the post (see {@link net.dv8tion.jda.api.utils.messages.MessageCreateBuilder MessageCreateBuilder}) + * + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the bot does not have {@link net.dv8tion.jda.api.Permission#MESSAGE_SEND Permission.MESSAGE_SEND} in the channel + * @throws IllegalArgumentException + *
      + *
    • If null is provided
    • + *
    • If the name is empty or longer than {@value Channel#MAX_NAME_LENGTH} characters
    • + *
    + * + * @return {@link ForumPostAction} + */ + @Nonnull + @Incubating + @CheckReturnValue + ForumPostAction createForumPost(@Nonnull String name, @Nonnull MessageCreateData message); + + /** + * The order used to sort forum posts. + */ + enum SortOrder + { + /** + * Sort by recent activity, including unarchive, message, reaction, and thread creation. + */ + RECENT_ACTIVITY(0), + /** + * Sort by the time the post was originally created. + */ + CREATION_TIME(1), + /** + * Placeholder for possible future order modes. + */ + UNKNOWN(-1), + ; + + private final int order; + + SortOrder(int order) + { + this.order = order; + } + + /** + * The underlying value as used by Discord. + * + * @return The raw order key + */ + public int getKey() + { + return order; + } + + /** + * The {@link SortOrder} for the provided key. + * + * @param key + * The key to get the {@link SortOrder} for + * + * @return The {@link SortOrder} for the provided key, or {@link #UNKNOWN} if the key is not known + */ + @Nonnull + public static SortOrder fromKey(int key) + { + for (SortOrder order : values()) + { + if (order.order == key) + return order; + } + + return UNKNOWN; + } + } +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/TextChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/TextChannel.java index 6cd02880f0..a821fa5515 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/TextChannel.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/TextChannel.java @@ -17,6 +17,7 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel; import net.dv8tion.jda.api.managers.channel.concrete.TextChannelManager; import net.dv8tion.jda.api.requests.restaction.ChannelAction; @@ -44,27 +45,8 @@ * @see JDA#getTextChannelsByName(String, boolean) * @see JDA#getTextChannelById(long) */ -public interface TextChannel extends StandardGuildMessageChannel +public interface TextChannel extends StandardGuildMessageChannel, ISlowmodeChannel { - /** - * The maximum duration of slowmode in seconds - */ - int MAX_SLOWMODE = 21600; - - /** - * The slowmode set for this TextChannel. - *
    If slowmode is set this returns an {@code int} between 1 and {@link TextChannel#MAX_SLOWMODE TextChannel.MAX_SLOWMODE}. - *
    If not set this returns {@code 0}. - * - *

    Note bots are unaffected by this. - *
    Having {@link net.dv8tion.jda.api.Permission#MESSAGE_MANAGE MESSAGE_MANAGE} or - * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} permission also - * grants immunity to slowmode. - * - * @return The slowmode for this TextChannel, between 1 and {@link TextChannel#MAX_SLOWMODE TextChannel.MAX_SLOWMODE}, or {@code 0} if no slowmode is set. - */ - int getSlowmode(); - @Nonnull @Override ChannelAction createCopy(@Nonnull Guild guild); diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/ThreadChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/ThreadChannel.java index c58cdff555..4f3c712b9c 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/ThreadChannel.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/ThreadChannel.java @@ -18,8 +18,12 @@ import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.ChannelField; +import net.dv8tion.jda.api.entities.channel.ChannelFlag; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.attribute.IMemberContainer; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.unions.GuildMessageChannelUnion; import net.dv8tion.jda.api.entities.channel.unions.IThreadContainerUnion; @@ -37,16 +41,28 @@ import java.util.Formatter; import java.util.List; -public interface ThreadChannel extends GuildMessageChannel, IMemberContainer +/** + * Represents Discord Message Threads of all kinds. + *

    This includes all thread channel types, namely: + *

      + *
    • {@link ChannelType#GUILD_PUBLIC_THREAD}
    • + *
    • {@link ChannelType#GUILD_PRIVATE_THREAD}
    • + *
    • {@link ChannelType#GUILD_NEWS_THREAD}
    • + *
    + * + *

    When a thread channel is {@link #isArchived() archived}, no new members can be added. + * You can use the {@link #getManager() manager} to {@link ThreadChannelManager#setArchived(boolean) unarchive} the thread. + * + * @see Guild#getThreadChannels() + * @see Guild#getThreadChannelById(long) + * @see Guild#getThreadChannelCache() + */ +public interface ThreadChannel extends GuildMessageChannel, IMemberContainer, ISlowmodeChannel { - //TODO fields that need to be researched: - // - rate_limit_per_user - // - last_pin_timestamp (do we even use this for Text/News channels?) - /** * Whether this thread is public or not. * - * Public threads can be read and joined by anyone with read access to its {@link net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer parent channel}. + *

    Public threads can be read and joined by anyone with read access to its {@link net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer parent channel}. * * @return true if this thread is public, false otherwise. */ @@ -58,19 +74,24 @@ default boolean isPublic() /** * Gets the current number of messages present in this thread. - *
    - * Threads started from seed messages in the {@link net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer parent channel} will not count that seed message. - *
    - * This will be capped at 50, regardless of actual count. + *
    Threads started from seed messages in the {@link net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer parent channel} will not count that seed message. + *
    This will be capped at 50 for threads created before July 1, 2022. * - * @return The number of messages sent in this channel, capping at 50. + * @return The number of messages sent in this thread */ int getMessageCount(); + /** + * The total number of messages sent in this thread, including all deleted messages. + *
    This might be inaccurate for threads created before July 1, 2022. + * + * @return The total number of messages ever sent in this thread + */ + int getTotalMessageCount(); + /** * Gets the current number of members that have joined this thread. - *
    - * This is capped at 50, meaning any additional members will not affect this count. + *
    This is capped at 50, meaning any additional members will not affect this count. * * @return The number of members that have joined this thread, capping at 50. */ @@ -91,12 +112,12 @@ default boolean isJoined() /** * Whether this thread is locked or not. * - * Locked threads cannot have new messages posted to them, or members join or leave them. + *

    Locked threads cannot have new messages posted to them, or members join or leave them. * Threads can only be locked and unlocked by moderators. * * @return true if this thread is locked, false otherwise. * - * @see ChannelField#LOCKED + * @see ChannelField#LOCKED */ boolean isLocked(); @@ -113,41 +134,57 @@ default boolean isJoined() * * @return true if this thread is invitable, false otherwise. * - * @see ChannelField#INVITABLE + * @see ChannelField#INVITABLE */ boolean isInvitable(); /** - * Gets the {@link net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer parent channel} of this thread. + * Whether this thread is a pinned forum post. * - * @see net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer#getThreadChannels() + * @return True, if this is a pinned forum post. + */ + default boolean isPinned() + { + return getFlags().contains(ChannelFlag.PINNED); + } + + /** + * Gets the {@link IThreadContainer parent channel} of this thread. * * @return The parent channel of this thread. + * + * @see IThreadContainer#getThreadChannels() */ @Nonnull IThreadContainerUnion getParentChannel(); - //todo-v5: document additional subclasses of GuildMessageChannel (VoiceChannels and ForumChannels, when needed) /** - * Gets the {@link GuildMessageChannel parent channel} of this thread, if it is a {@link TextChannel} or {@link NewsChannel}. - *
    - * This is a convenience method that will perform the cast if possible, throwing otherwise. - * - * @return The parent channel of this thread, as a {@link GuildMessageChannel}. + * Gets the {@link GuildMessageChannelUnion parent channel} of this thread, if it is a {@link TextChannel}, {@link NewsChannel}, or {@link VoiceChannel}. + *
    This is a convenience method that will perform the cast if possible, throwing otherwise. * * @throws UnsupportedOperationException * If the parent channel is not a {@link GuildMessageChannel}. + * + * @return The parent channel of this thread, as a {@link GuildMessageChannelUnion}. */ @Nonnull default GuildMessageChannelUnion getParentMessageChannel() { - if (getParentChannel() instanceof GuildMessageChannel) { + if (getParentChannel() instanceof GuildMessageChannel) return (GuildMessageChannelUnion) getParentChannel(); - } - throw new UnsupportedOperationException("Parent of this thread is not a MessageChannel. Parent is type: " + getParentChannel().getType().getId()); + throw new UnsupportedOperationException("Parent of this thread is not a MessageChannel. Parent: " + getParentChannel()); } + /** + * The {@link net.dv8tion.jda.api.entities.channel.forums.ForumTag forum tags} applied to this thread. + *
    This will be an empty list if the thread was not created in a {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannel}. + * + * @return Immutable {@link List} of {@link net.dv8tion.jda.api.entities.channel.forums.ForumTag ForumTags} applied to this post + */ + @Nonnull + List getAppliedTags(); + /** * Attempts to get the {@link net.dv8tion.jda.api.entities.Message Message} from Discord's servers that started this thread. * @@ -192,11 +229,11 @@ default GuildMessageChannelUnion getParentMessageChannel() /** * Gets the self member, as a member of this thread. * - *
    If the current account is not a member of this thread, this will return null. + *

    If the current account is not a member of this thread, this will return null. * * @return The self member of this thread, null if the current account is not a member of this thread. * - * @see #isJoined() + * @see #isJoined() */ @Nullable default ThreadMember getSelfThreadMember() @@ -207,8 +244,8 @@ default ThreadMember getSelfThreadMember() /** * Gets a List of all cached {@link ThreadMember members} of this thread. - *
    - *
    The thread owner is not included in this list, unless the current account is the owner. + * + *

    The thread owner is not included in this list, unless the current account is the owner. * Any updates to this cache are lost when JDA is shutdown, and this list is not sent to JDA on startup. * For this reason, {@link #retrieveThreadMembers()} should be used instead in most cases. * @@ -221,9 +258,9 @@ default ThreadMember getSelfThreadMember() *

  • the bot must have be online to receive the update
  • * * - * @return a List of all {@link ThreadMember members} of this thread. This list may be empty, but not null. + * @return List of all {@link ThreadMember members} of this thread. This list may be empty, but not null. * - * @see #retrieveThreadMembers() + * @see #retrieveThreadMembers() */ @Nonnull List getThreadMembers(); @@ -231,20 +268,20 @@ default ThreadMember getSelfThreadMember() /** * Gets a {@link ThreadMember} of this thread by their {@link Member}. * - * Note that this operation relies on the {@link #getThreadMembers() ThreadMember cache} for this ThreadChannel. + *

    Note that this operation relies on the {@link #getThreadMembers() ThreadMember cache} for this ThreadChannel. * As the cache is likely to be unpopulated, this method is likely to return null. * - * Use of {@link #retrieveThreadMember(Member)} is preferred instead, once it is released. - * - * @param member - * The member to get the {@link ThreadMember} for. + *

    Use of {@link #retrieveThreadMember(Member)} is preferred instead, once it is released. * - * @return The {@link ThreadMember} of this thread for the given member. + * @param member + * The member to get the {@link ThreadMember} for. * * @throws IllegalArgumentException * If the given member is null. * - * @see #retrieveThreadMember(Member) + * @return The {@link ThreadMember} of this thread for the given member. + * + * @see #retrieveThreadMember(Member) */ @Nullable default ThreadMember getThreadMember(Member member) @@ -256,20 +293,20 @@ default ThreadMember getThreadMember(Member member) /** * Gets a {@link ThreadMember} of this thread by their {@link Member}. * - * Note that this operation relies on the {@link #getThreadMembers() ThreadMember cache} for this ThreadChannel. + *

    Note that this operation relies on the {@link #getThreadMembers() ThreadMember cache} for this ThreadChannel. * As the cache is likely to be unpopulated, this method is likely to return null. * - * Use of {@link #retrieveThreadMember(Member)} is preferred instead, once it is released. + *

    Use of {@link #retrieveThreadMember(Member)} is preferred instead, once it is released. * - * @param user - * The user to get the {@link ThreadMember} for. - * - * @return The {@link ThreadMember} of this thread for the given member. + * @param user + * The user to get the {@link ThreadMember} for. * * @throws IllegalArgumentException * If the given user is null. * - * @see #retrieveThreadMember(Member) + * @return The {@link ThreadMember} of this thread for the given member. + * + * @see #retrieveThreadMember(Member) */ @Nullable default ThreadMember getThreadMember(User user) @@ -281,20 +318,20 @@ default ThreadMember getThreadMember(User user) /** * Gets a {@link ThreadMember} of this thread by their {@link Member}. * - * Note that this operation relies on the {@link #getThreadMembers() ThreadMember cache} for this ThreadChannel. + *

    Note that this operation relies on the {@link #getThreadMembers() ThreadMember cache} for this ThreadChannel. * As the cache is likely to be unpopulated, this method is likely to return null. * - * Use of {@link #retrieveThreadMember(Member)} is preferred instead, once it is released. + *

    Use of {@link #retrieveThreadMember(Member)} is preferred instead, once it is released. * * @param id * The ID of the member to get the {@link ThreadMember} for. * - * @return The {@link ThreadMember} of this thread for the given member. - * * @throws IllegalArgumentException * If the given id is null or empty. * - * @see #retrieveThreadMember(Member) + * @return The {@link ThreadMember} of this thread for the given member. + * + * @see #retrieveThreadMember(Member) */ @Nullable default ThreadMember getThreadMemberById(String id) @@ -305,17 +342,17 @@ default ThreadMember getThreadMemberById(String id) /** * Gets a {@link ThreadMember} of this thread by their {@link Member}. * - * Note that this operation relies on the {@link #getThreadMembers() ThreadMember cache} for this ThreadChannel. + *

    Note that this operation relies on the {@link #getThreadMembers() ThreadMember cache} for this ThreadChannel. * As the cache is likely to be unpopulated, this method is likely to return null. * - * Use of {@link #retrieveThreadMember(Member)} is preferred instead, once it is released. + *

    Use of {@link #retrieveThreadMember(Member)} is preferred instead, once it is released. * * @param id * The member to get the {@link ThreadMember} for. * * @return The {@link ThreadMember} of this thread for the given member. * - * @see #retrieveThreadMember(Member) + * @see #retrieveThreadMember(Member) */ @Nullable ThreadMember getThreadMemberById(long id); @@ -405,7 +442,7 @@ default CacheRestAction retrieveThreadMemberById(@Nonnull String i /** * Retrieves the {@link ThreadMember ThreadMembers} of this thread. * - * This requires the {@link net.dv8tion.jda.api.requests.GatewayIntent#GUILD_MEMBERS} intent to be enabled. + *

    This requires the {@link net.dv8tion.jda.api.requests.GatewayIntent#GUILD_MEMBERS} intent to be enabled. * * @return a RestAction that resolves into a List of {@link ThreadMember ThreadMembers} of this thread. */ @@ -443,13 +480,14 @@ default String getOwnerId() /** * Gets the {@link Member} that created and owns this thread. - *
    - * This will be null if the member is not cached, and so it is recommended to {@link Guild#retrieveMemberById(long) retrieve this member from the guild} using {@link #getOwnerIdLong() the owner'd ID}. + *
    This will be null if the member is not cached, + * and so it is recommended to {@link Guild#retrieveMemberById(long) retrieve this member from the guild} + * using {@link #getOwnerIdLong() the owner'd ID}. * * @return The {@link Member} of the member who created this thread. * - * @see #getThreadMemberById(long) - * @see Guild#retrieveMemberById(long) + * @see #getThreadMemberById(long) + * @see Guild#retrieveMemberById(long) */ @Nullable default Member getOwner() @@ -459,14 +497,15 @@ default Member getOwner() /** * Gets the owner of this thread as a {@link ThreadMember}. - *
    - * This will be null if the member is not cached, and so it is recommended to retrieve the owner instead. + *
    This will be null if the member is not cached, and so it is recommended to retrieve the owner instead. * - *
    This method relies on the {@link #getThreadMembers()} cache, and so it is recommended to {@link #retrieveThreadMemberById(long) retrieve the ThreadMember} by {@link #getOwnerIdLong() their ID} instead. + *

    This method relies on the {@link #getThreadMembers()} cache, + * and so it is recommended to {@link #retrieveThreadMemberById(long) retrieve the ThreadMember} + * by {@link #getOwnerIdLong() their ID} instead. * * @return The owner of this thread as a {@link ThreadMember}. * - * @see #getThreadMemberById(long) + * @see #getThreadMemberById(long) */ @Nullable default ThreadMember getOwnerThreadMember() @@ -477,7 +516,7 @@ default ThreadMember getOwnerThreadMember() /** * Whether this thread has been archived. * - * This method will consider locked channels to also be archived. + *

    This method will consider locked channels to also be archived. * *

    Archived threads are not deleted threads, but are considered inactive. * They are not shown to clients in the channels list, but can still be navigated to and read. @@ -485,10 +524,10 @@ default ThreadMember getOwnerThreadMember() * * @return true if this thread has been archived, false otherwise. * - * @see #isLocked() - * @see ThreadChannelManager#setArchived(boolean) - * @see #getAutoArchiveDuration() - * @see ChannelField#ARCHIVED + * @see #isLocked() + * @see ThreadChannelManager#setArchived(boolean) + * @see #getAutoArchiveDuration() + * @see ChannelField#ARCHIVED */ boolean isArchived(); @@ -504,20 +543,20 @@ default ThreadMember getOwnerThreadMember() * * @return the time of the last archive info update. * - * @see ChannelField#ARCHIVED_TIMESTAMP + * @see ChannelField#ARCHIVED_TIMESTAMP */ OffsetDateTime getTimeArchiveInfoLastModified(); /** * The inactivity timeout of this thread. * - * If a message is not sent within this amount of time, the thread will be automatically archived. + *

    If a message is not sent within this amount of time, the thread will be automatically hidden. * - * A thread archived this way can be unarchived by any member. + *

    A thread archived this way can be unarchived by any member. * - * @return the time before which a thread will automatically be archived. + * @return The inactivity timeframe until a thread is automatically hidden. * - * @see ChannelField#AUTO_ARCHIVE_DURATION + * @see ChannelField#AUTO_ARCHIVE_DURATION */ @Nonnull AutoArchiveDuration getAutoArchiveDuration(); @@ -532,19 +571,10 @@ default ThreadMember getOwnerThreadMember() @Nonnull OffsetDateTime getTimeCreated(); - /** - * The slowmode time of this thread. This determines the time each non-moderator must wait before sending another message. - * - * @return The amount of time in seconds a ThreadMember must wait between sending messages. - * - * @see net.dv8tion.jda.api.managers.channel.concrete.ThreadChannelManager#setSlowmode(int) - */ - int getSlowmode(); - /** * Joins this thread, adding the current account to the member list of this thread. * - * Note that joining threads is not a requirement of getting events about the thread. + *

    Note that joining threads is not a requirement of getting events about the thread. * *
    This will have no effect if the current account is already a member of this thread. * @@ -569,7 +599,6 @@ default ThreadMember getOwnerThreadMember() /** * Leaves this thread, removing the current account from the member list of this thread. - * *
    This will have no effect if the current account is not a member of this thread. * *

    The following {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} are possible: @@ -594,7 +623,6 @@ default ThreadMember getOwnerThreadMember() //this is probably also affected by private threads that are not invitable /** * Adds a member to this thread. - * *
    This will have no effect if the member is already a member of this thread. * *

    The following {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} are possible: @@ -656,16 +684,14 @@ default ThreadMember getOwnerThreadMember() * *

  • {@link net.dv8tion.jda.api.requests.ErrorResponse#INVALID_FORM_BODY INVALID_FORM_BODY} *
    The provided User ID is not a valid snowflake.
  • - * * * * @param id * The id of the member to add. * * @throws IllegalStateException - * If this thread is locked or archived. - * - * @throws NumberFormatException + * If this thread is locked or archived + * @throws IllegalArgumentException * If the provided id is not a valid snowflake. * * @return {@link RestAction} @@ -678,7 +704,6 @@ default RestAction addThreadMemberById(@Nonnull String id) /** * Adds a member to this thread. - * *
    This will have no effect if the member is already a member of this thread. * *

    The following {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} are possible: @@ -695,7 +720,6 @@ default RestAction addThreadMemberById(@Nonnull String id) * *

  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL} *
    The request was attempted after the channel was deleted.
  • - * * * * @param user @@ -703,9 +727,8 @@ default RestAction addThreadMemberById(@Nonnull String id) * * @throws IllegalStateException * If this thread is locked or archived. - * * @throws IllegalArgumentException - * If the provided user was null. + * If the provided user is null. * * @return {@link RestAction} */ @@ -718,7 +741,6 @@ default RestAction addThreadMember(@Nonnull User user) /** * Adds a member to this thread. - * *
    This will have no effect if the member is already a member of this thread. * *

    The following {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} are possible: @@ -735,7 +757,6 @@ default RestAction addThreadMember(@Nonnull User user) * *

  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL} *
    The request was attempted after the channel was deleted.
  • - * * * * @param member @@ -743,9 +764,8 @@ default RestAction addThreadMember(@Nonnull User user) * * @throws IllegalStateException * If this thread is locked or archived. - * * @throws IllegalArgumentException - * If the provided member was null. + * If the provided member is null. * * @return {@link RestAction} */ @@ -779,11 +799,12 @@ default RestAction addThreadMember(@Nonnull Member member) * * * - * @param id - * The id of the member to remove from this thread. + * @param id + * The id of the member to remove from this thread. * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS} permission, and this isn't a private thread channel this account owns. + * If the account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS} permission, + * and this is not a private thread channel this account owns. * * @return {@link RestAction} */ @@ -813,13 +834,13 @@ default RestAction addThreadMember(@Nonnull Member member) * * * - * @param id - * The id of the member to remove from this thread. + * @param id + * The id of the member to remove from this thread. * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS} permission, and this isn't a private thread channel this account owns. - * - * @throws NumberFormatException + * If the account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS} permission, + * and this is not a private thread channel this account owns. + * @throws IllegalArgumentException * If the provided id is not a valid snowflake. * * @return {@link RestAction} @@ -847,14 +868,14 @@ default RestAction removeThreadMemberById(@Nonnull String id) * * * - * @param user - * The user to remove from this thread. + * @param user + * The user to remove from this thread. * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS} permission, and this isn't a private thread channel this account owns. - * + * If the account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS} permission, + * and this is not a private thread channel this account owns. * @throws IllegalArgumentException - * If the provided user was null. + * If the provided user is null. * * @return {@link RestAction} */ @@ -888,9 +909,8 @@ default RestAction removeThreadMember(@Nonnull User user) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException * If the account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS} permission, and this isn't a private thread channel this account owns. - * * @throws IllegalArgumentException - * If the provided member was null. + * If the provided member is null. * * @return {@link RestAction} */ @@ -921,19 +941,17 @@ default void formatTo(Formatter formatter, int flags, int width, int precision) MiscUtil.appendTo(formatter, width, precision, leftJustified, out); } - ////////////////////////// - /** * The values permitted for the auto archive duration of a {@link ThreadChannel}. * - * This is the time before an idle thread will be automatically archived. + *

    This is the time before an idle thread will be automatically hidden. * - * Sending a message to the thread will reset the timer. + *

    Sending a message to the thread will reset the timer. * * @see ChannelField#AUTO_ARCHIVE_DURATION */ - enum AutoArchiveDuration { - //TODO: I dislike this naming scheme. Need to come up with something better. + enum AutoArchiveDuration + { TIME_1_HOUR(60), TIME_24_HOURS(1440), TIME_3_DAYS(4320), @@ -946,11 +964,27 @@ enum AutoArchiveDuration { this.minutes = minutes; } + /** + * The number of minutes before an idle thread will be automatically hidden. + * + * @return The number of minutes + */ public int getMinutes() { return minutes; } + /** + * Provides the corresponding enum constant for the provided number of minutes. + * + * @param minutes + * The number of minutes. (must be one of the valid values) + * + * @throws IllegalArgumentException + * If the provided minutes is not a valid value. + * + * @return The corresponding enum constant. + */ @Nonnull public static AutoArchiveDuration fromKey(int minutes) { diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/forums/BaseForumTag.java b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/BaseForumTag.java new file mode 100644 index 0000000000..13c6d48fc6 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/BaseForumTag.java @@ -0,0 +1,76 @@ +/* + * 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.api.entities.channel.forums; + +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.api.utils.data.SerializableData; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Information describing a forum tag. + *
    This is an abstraction used to simplify managing tags. + * + * @see ForumTag + * @see ForumTagData + * @see ForumTagSnowflake + */ +public interface BaseForumTag extends SerializableData +{ + /** + * The name of the tag. + * + * @return The name + */ + @Nonnull + String getName(); + + /** + * Whether this tag can only be applied by moderators with the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS MANAGE_THREADS} permission (aka Manage Posts). + * + * @return True, if this tag can only be applied by moderators with the required permission + */ + boolean isModerated(); + + /** + * The emoji used as the tag icon. + *
    For custom emoji, this will have an empty name and {@link CustomEmoji#isAnimated()} is always {@code false}, due to discord chicanery. + * + * @return {@link EmojiUnion} representing the tag emoji, or null if no emoji is applied. + */ + @Nullable + EmojiUnion getEmoji(); + + @Nonnull + @Override + default DataObject toData() + { + DataObject json = DataObject.empty() + .put("name", getName()) + .put("moderated", isModerated()); + EmojiUnion emoji = getEmoji(); + if (emoji instanceof UnicodeEmoji) + json.put("emoji_name", emoji.getName()); + else if (emoji instanceof CustomEmoji) + json.put("emoji_id", ((CustomEmoji) emoji).getId()); + return json; + } +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumPost.java b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumPost.java new file mode 100644 index 0000000000..b2b6b58f38 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumPost.java @@ -0,0 +1,67 @@ +/* + * 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.api.entities.channel.forums; + +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import javax.annotation.Nonnull; + +/** + * Result of creating a post in a {@link ForumChannel}. + * + * @see ForumChannel#createForumPost(String, MessageCreateData) + * @see #getThreadChannel() + * @see #getMessage() + */ +public class ForumPost +{ + private final Message message; + private final ThreadChannel thread; + + public ForumPost(@Nonnull Message message, @Nonnull ThreadChannel thread) + { + this.message = message; + this.thread = thread; + } + + /** + * The starter message of the post. + *
    This is created from the {@link MessageCreateData} passed to {@link ForumChannel#createForumPost(String, MessageCreateData)}. + * + * @return {@link Message} + */ + @Nonnull + public Message getMessage() + { + return message; + } + + /** + * The {@link ThreadChannel} of the post. + *
    This will use the name provided to {@link ForumChannel#createForumPost(String, MessageCreateData)}. + * + * @return The forum post thread channel + */ + @Nonnull + public ThreadChannel getThreadChannel() + { + return thread; + } +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTag.java b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTag.java new file mode 100644 index 0000000000..989dfde283 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTag.java @@ -0,0 +1,56 @@ +/* + * 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.api.entities.channel.forums; + +import net.dv8tion.jda.api.entities.ISnowflake; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.internal.utils.Checks; + +import javax.annotation.Nonnull; + +/** + * Represents a Discord Forum Tag. + *
    These tags can be applied to forum posts to help categorize them. + */ +public interface ForumTag extends ISnowflake, Comparable, BaseForumTag +{ + /** + * The maximum length of a forum tag name ({@value #MAX_NAME_LENGTH}) + */ + int MAX_NAME_LENGTH = 20; + + /** + * The tag position, used for sorting. + * + * @return The tag position. + */ + int getPosition(); + + @Override + default int compareTo(@Nonnull ForumTag o) + { + Checks.notNull(o, "ForumTag"); + return Integer.compare(getPosition(), o.getPosition()); + } + + @Nonnull + @Override + default DataObject toData() + { + return BaseForumTag.super.toData().put("id", getId()); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTagData.java b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTagData.java new file mode 100644 index 0000000000..d2fd24efd2 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTagData.java @@ -0,0 +1,167 @@ +/* + * 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.api.entities.channel.forums; + +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.managers.channel.concrete.ForumChannelManager; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.internal.utils.Checks; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +/** + * Data class used to create or update existing forum tags. + * + * @see ForumChannelManager#setAvailableTags(List) + */ +public class ForumTagData implements BaseForumTag +{ + private String name; + private Emoji emoji; + private boolean moderated; + private long id; + + /** + * Create a new {@link ForumTagData} instance. + * + * @param name + * The tag name (1-{@value ForumTag#MAX_NAME_LENGTH} characters) + * + * @throws IllegalArgumentException + * If the provided name is null or not between 1 and {@value ForumTag#MAX_NAME_LENGTH} characters long + */ + public ForumTagData(@Nonnull String name) + { + setName(name); + } + + /** + * Creates a new {@link ForumTagData} instance based on the provided {@link BaseForumTag}. + *
    This also binds to the id of the provided tag, if available. + * + * @param tag + * The base tag to use + * + * @throws IllegalArgumentException + * If null is provided or the tag has an invalid name + * + * @return The new {@link ForumTagData} instance + */ + @Nonnull + public static ForumTagData from(@Nonnull BaseForumTag tag) + { + Checks.notNull(tag, "Tag"); + ForumTagData data = new ForumTagData(tag.getName()) + .setEmoji(tag.getEmoji()) + .setModerated(tag.isModerated()); + if (tag instanceof ForumTagSnowflake) + data.id = ((ForumTagSnowflake) tag).getIdLong(); + return data; + } + + /** + * Set the new tag name to use. + * + * @param name + * The new tag name (1-{@value ForumTag#MAX_NAME_LENGTH} characters) + * + * @throws IllegalArgumentException + * If the provided name is null or not between 1 and {@value ForumTag#MAX_NAME_LENGTH} characters long + * + * @return The updated ForumTagData instance + */ + @Nonnull + public ForumTagData setName(@Nonnull String name) + { + Checks.notEmpty(name, "Name"); + Checks.notLonger(name, ForumTag.MAX_NAME_LENGTH, "Name"); + this.name = name; + return this; + } + + /** + * Set whether the tag can only be applied by forum moderators. + * + * @param moderated + * True, if the tag is restricted to moderators + * + * @return The updated ForumTagData instance + * + * @see #isModerated() + */ + @Nonnull + public ForumTagData setModerated(boolean moderated) + { + this.moderated = moderated; + return this; + } + + /** + * Set the emoji to use for this tag. + *
    This emoji is displayed as an icon attached to the tag. + * + * @param emoji + * The emoji icon of the tag + * + * @return The updated ForumTagData instance + */ + @Nonnull + public ForumTagData setEmoji(@Nullable Emoji emoji) + { + this.emoji = emoji; + return this; + } + + @Nonnull + @Override + public String getName() + { + return name; + } + + @Override + public boolean isModerated() + { + return moderated; + } + + @Nullable + @Override + public EmojiUnion getEmoji() + { + return (EmojiUnion) emoji; + } + + @Nonnull + @Override + public DataObject toData() + { + DataObject json = BaseForumTag.super.toData(); + if (id != 0) + json.put("id", Long.toUnsignedString(id)); + return json; + } + + @Override + public String toString() + { + return toData().toString(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTagSnowflake.java b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTagSnowflake.java new file mode 100644 index 0000000000..76074da16d --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/forums/ForumTagSnowflake.java @@ -0,0 +1,63 @@ +/* + * 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.api.entities.channel.forums; + +import net.dv8tion.jda.api.entities.ISnowflake; +import net.dv8tion.jda.api.requests.restaction.ForumPostAction; +import net.dv8tion.jda.api.utils.MiscUtil; +import net.dv8tion.jda.internal.entities.ForumTagSnowflakeImpl; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * Minimal representation for a forum tag. + *
    This is primarily useful for creating posts with {@link ForumPostAction#setTags(Collection)}. + */ +public interface ForumTagSnowflake extends ISnowflake +{ + /** + * Wraps the provided id into a ForumTagSnowflake instance. + * + * @param id + * The id of an existing forum tag + * + * @return ForumTagSnowflake instance for the provided id + */ + @Nonnull + static ForumTagSnowflake fromId(long id) + { + return new ForumTagSnowflakeImpl(id); + } + + /** + * Wraps the provided id into a ForumTagSnowflake instance. + * + * @param id + * The id of an existing forum tag + * + * @throws IllegalArgumentException + * If the provided id is not a valid snowflake + * + * @return ForumTagSnowflake instance for the provided id + */ + @Nonnull + static ForumTagSnowflake fromId(@Nonnull String id) + { + return new ForumTagSnowflakeImpl(MiscUtil.parseSnowflake(id)); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/GuildChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/GuildChannel.java index cb36b6cddd..03683a09dd 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/GuildChannel.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/GuildChannel.java @@ -67,7 +67,6 @@ public interface GuildChannel extends Channel, Comparable ChannelManager getManager(); /** - * TODO-v5: this override might not be needed anymore if we remove AuditableRestAction and instead place auditable hooks onto RestAction itself. * Deletes this GuildChannel. * *

    Possible ErrorResponses include: @@ -95,6 +94,7 @@ public interface GuildChannel extends Channel, Comparable AuditableRestAction delete(); //TODO-v5: Docs + @Nonnull IPermissionContainer getPermissionContainer(); /** diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/StandardGuildMessageChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/StandardGuildMessageChannel.java index 9521f92df3..c0826c8d4c 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/StandardGuildMessageChannel.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/StandardGuildMessageChannel.java @@ -20,6 +20,7 @@ import net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel; import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; @@ -44,6 +45,12 @@ */ public interface StandardGuildMessageChannel extends StandardGuildChannel, GuildMessageChannel, IThreadContainer, IWebhookContainer, IAgeRestrictedChannel { + /** + * The maximum length a channel topic can be ({@value #MAX_TOPIC_LENGTH}) + *
    Forum channels have a higher limit, defined by {@link ForumChannel#MAX_FORUM_TOPIC_LENGTH} + */ + int MAX_TOPIC_LENGTH = 1024; + @Nonnull @Override StandardGuildMessageChannelManager getManager(); diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/ChannelUnion.java b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/ChannelUnion.java index 2acf963fe3..980b1c4e57 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/ChannelUnion.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/ChannelUnion.java @@ -37,6 +37,7 @@ *

  • {@link ThreadChannel}
  • *
  • {@link VoiceChannel}
  • *
  • {@link StageChannel}
  • + *
  • {@link ForumChannel}
  • *
  • {@link Category}
  • * */ @@ -175,6 +176,28 @@ public interface ChannelUnion extends Channel @Nonnull StageChannel asStageChannel(); + /** + * Casts this union to a {@link ForumChannel}. + * This method exists for developer discoverability. + * + * Note: This is effectively equivalent to using the cast operator: + *
    
    +     * //These are the same!
    +     * ForumChannel channel = union.asForumChannel();
    +     * ForumChannel channel2 = (ForumChannel) union;
    +     * 
    + * + * You can use {@link #getType()} to see if the channel is of type {@link ChannelType#FORUM} to validate + * whether you can call this method in addition to normal instanceof checks: channel instanceof ForumChannel + * + * @throws IllegalStateException + * If the channel represented by this union is not actually a {@link ForumChannel}. + * + * @return The channel as a {@link ForumChannel} + */ + @Nonnull + ForumChannel asForumChannel(); + /** * Casts this union to a {@link Category}. * This method exists for developer discoverability. diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/GuildChannelUnion.java b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/GuildChannelUnion.java index 170233e0c5..afa6b4c7d3 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/GuildChannelUnion.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/GuildChannelUnion.java @@ -35,6 +35,7 @@ *
  • {@link ThreadChannel}
  • *
  • {@link VoiceChannel}
  • *
  • {@link StageChannel}
  • + *
  • {@link ForumChannel}
  • *
  • {@link Category}
  • * */ @@ -172,6 +173,28 @@ public interface GuildChannelUnion extends GuildChannel @Nonnull Category asCategory(); + /** + * Casts this union to a {@link ForumChannel}. + * This method exists for developer discoverability. + * + * Note: This is effectively equivalent to using the cast operator: + *
    
    +     * //These are the same!
    +     * ForumChannel channel = union.asForumChannel();
    +     * ForumChannel channel2 = (ForumChannel) union;
    +     * 
    + * + * You can use {@link #getType()} to see if the channel is of type {@link ChannelType#FORUM} to validate + * whether you can call this method in addition to normal instanceof checks: channel instanceof ForumChannel + * + * @throws IllegalStateException + * If the channel represented by this union is not actually a {@link ForumChannel}. + * + * @return The channel as a {@link ForumChannel} + */ + @Nonnull + ForumChannel asForumChannel(); + /** * Casts this union to a {@link GuildMessageChannel}. * This method exists for developer discoverability. diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/GuildMessageChannelUnion.java b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/GuildMessageChannelUnion.java index c13488e26e..3dbb14503c 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/GuildMessageChannelUnion.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/GuildMessageChannelUnion.java @@ -37,6 +37,7 @@ *
      *
    • {@link TextChannel}
    • *
    • {@link NewsChannel}
    • + *
    • {@link VoiceChannel}
    • *
    • {@link ThreadChannel}
    • *
    */ diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IPermissionContainerUnion.java b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IPermissionContainerUnion.java index 28d8755f8d..85bb3c9e0e 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IPermissionContainerUnion.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IPermissionContainerUnion.java @@ -38,6 +38,7 @@ *
  • {@link NewsChannel}
  • *
  • {@link VoiceChannel}
  • *
  • {@link StageChannel}
  • + *
  • {@link ForumChannel}
  • *
  • {@link Category}
  • * */ @@ -153,6 +154,28 @@ public interface IPermissionContainerUnion extends IPermissionContainer @Nonnull Category asCategory(); + /** + * Casts this union to a {@link ForumChannel}. + * This method exists for developer discoverability. + * + * Note: This is effectively equivalent to using the cast operator: + *
    
    +     * //These are the same!
    +     * ForumChannel channel = union.asForumChannel();
    +     * ForumChannel channel2 = (ForumChannel) union;
    +     * 
    + * + * You can use {@link #getType()} to see if the channel is of type {@link ChannelType#FORUM} to validate + * whether you can call this method in addition to normal instanceof checks: channel instanceof ForumChannel + * + * @throws IllegalStateException + * If the channel represented by this union is not actually a {@link ForumChannel}. + * + * @return The channel as a {@link ForumChannel} + */ + @Nonnull + ForumChannel asForumChannel(); + /** * Casts this union to a {@link GuildMessageChannel}. * This method exists for developer discoverability. diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IThreadContainerUnion.java b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IThreadContainerUnion.java index 18546a9240..688403c9ee 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IThreadContainerUnion.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IThreadContainerUnion.java @@ -18,6 +18,7 @@ import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; @@ -35,6 +36,7 @@ *
      *
    • {@link TextChannel}
    • *
    • {@link NewsChannel}
    • + *
    • {@link ForumChannel}
    • *
    */ public interface IThreadContainerUnion extends IThreadContainer @@ -83,7 +85,27 @@ public interface IThreadContainerUnion extends IThreadContainer @Nonnull NewsChannel asNewsChannel(); - //TODO: Add asForumChannel + /** + * Casts this union to a {@link ForumChannel}. + * This method exists for developer discoverability. + * + * Note: This is effectively equivalent to using the cast operator: + *
    
    +     * //These are the same!
    +     * ForumChannel channel = union.asForumChannel();
    +     * ForumChannel channel2 = (ForumChannel) union;
    +     * 
    + * + * You can use {@link #getType()} to see if the channel is of type {@link ChannelType#FORUM} to validate + * whether you can call this method in addition to normal instanceof checks: channel instanceof ForumChannel + * + * @throws IllegalStateException + * If the channel represented by this union is not actually a {@link ForumChannel}. + * + * @return The channel as a {@link ForumChannel} + */ + @Nonnull + ForumChannel asForumChannel(); /** * Casts this union to a {@link GuildMessageChannel}. diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IWebhookContainerUnion.java b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IWebhookContainerUnion.java index 1b142e17d7..7715e494c1 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IWebhookContainerUnion.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/IWebhookContainerUnion.java @@ -19,6 +19,7 @@ import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; @@ -37,6 +38,7 @@ *
      *
    • {@link TextChannel}
    • *
    • {@link NewsChannel}
    • + *
    • {@link ForumChannel}
    • *
    */ public interface IWebhookContainerUnion extends IWebhookContainer @@ -85,7 +87,27 @@ public interface IWebhookContainerUnion extends IWebhookContainer @Nonnull NewsChannel asNewsChannel(); - //TODO: Add asForumChannel + /** + * Casts this union to a {@link ForumChannel}. + * This method exists for developer discoverability. + * + * Note: This is effectively equivalent to using the cast operator: + *
    
    +     * //These are the same!
    +     * ForumChannel channel = union.asForumChannel();
    +     * ForumChannel channel2 = (ForumChannel) union;
    +     * 
    + * + * You can use {@link #getType()} to see if the channel is of type {@link ChannelType#FORUM} to validate + * whether you can call this method in addition to normal instanceof checks: channel instanceof ForumChannel + * + * @throws IllegalStateException + * If the channel represented by this union is not actually a {@link ForumChannel}. + * + * @return The channel as a {@link ForumChannel} + */ + @Nonnull + ForumChannel asForumChannel(); /** * Casts this union to a {@link net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer}. diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/MessageChannelUnion.java b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/MessageChannelUnion.java index 61be48620f..faa2a08830 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/unions/MessageChannelUnion.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/unions/MessageChannelUnion.java @@ -33,6 +33,7 @@ *
      *
    • {@link TextChannel}
    • *
    • {@link NewsChannel}
    • + *
    • {@link VoiceChannel}
    • *
    • {@link ThreadChannel}
    • *
    • {@link PrivateChannel}
    • *
    diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/forum/ForumTagAddEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/forum/ForumTagAddEvent.java new file mode 100644 index 0000000000..d171be2f0e --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/forum/ForumTagAddEvent.java @@ -0,0 +1,39 @@ +/* + * 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.api.events.channel.forum; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * Indicates that a new {@link ForumTag} was added to a {@link ForumChannel}. + * + *

    Requirements
    + * This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * {@link net.dv8tion.jda.api.JDABuilder#createLight(String, Collection) JDABuilder.createLight(...)} disables this by default. + */ +public class ForumTagAddEvent extends GenericForumTagEvent +{ + public ForumTagAddEvent(@Nonnull JDA api, long responseNumber, @Nonnull ForumChannel channel, @Nonnull ForumTag tag) + { + super(api, responseNumber, channel, tag); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/forum/ForumTagRemoveEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/forum/ForumTagRemoveEvent.java new file mode 100644 index 0000000000..10f49db772 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/forum/ForumTagRemoveEvent.java @@ -0,0 +1,39 @@ +/* + * 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.api.events.channel.forum; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * Indicates that a {@link ForumTag} was removed from a {@link ForumChannel}. + * + *

    Requirements
    + * This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * {@link net.dv8tion.jda.api.JDABuilder#createLight(String, Collection) JDABuilder.createLight(...)} disables this by default. + */ +public class ForumTagRemoveEvent extends GenericForumTagEvent +{ + public ForumTagRemoveEvent(@Nonnull JDA api, long responseNumber, @Nonnull ForumChannel channel, @Nonnull ForumTag tag) + { + super(api, responseNumber, channel, tag); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/forum/GenericForumTagEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/forum/GenericForumTagEvent.java new file mode 100644 index 0000000000..64f3ff4cba --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/forum/GenericForumTagEvent.java @@ -0,0 +1,68 @@ +/* + * 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.api.events.channel.forum; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; +import net.dv8tion.jda.api.events.Event; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * Abstraction of all tags relating to {@link ForumTag} changes (excluding {@link ThreadChannel#getAppliedTags()}). + * + *

    Requirements
    + * This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * {@link net.dv8tion.jda.api.JDABuilder#createLight(String, Collection) JDABuilder.createLight(...)} disables this by default. + */ +public abstract class GenericForumTagEvent extends Event +{ + protected final ForumChannel channel; + protected final ForumTag tag; + + public GenericForumTagEvent(@Nonnull JDA api, long responseNumber, @Nonnull ForumChannel channel, @Nonnull ForumTag tag) + { + super(api, responseNumber); + this.channel = channel; + this.tag = tag; + } + + /** + * The {@link ForumChannel} which has been updated. + * + * @return The {@link ForumChannel} + */ + @Nonnull + public ForumChannel getChannel() + { + return channel; + } + + /** + * The {@link ForumTag} that was affected by this event + * + * @return The {@link ForumTag} + */ + @Nonnull + public ForumTag getTag() + { + return tag; + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateEmojiEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateEmojiEvent.java new file mode 100644 index 0000000000..26b77296cf --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateEmojiEvent.java @@ -0,0 +1,67 @@ +/* + * 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.api.events.channel.forum.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; + +/** + * Indicates that the {@link ForumTag#getEmoji() emoji} of a {@link ForumTag} changed. + * + *

    Requirements
    + * This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * {@link net.dv8tion.jda.api.JDABuilder#createLight(String, Collection) JDABuilder.createLight(...)} disables this by default. + * + *

    Identifier: {@code emoji} + */ +public class ForumTagUpdateEmojiEvent extends GenericForumTagUpdateEvent +{ + public static final String IDENTIFIER = "emoji"; + + public ForumTagUpdateEmojiEvent(@Nonnull JDA api, long responseNumber, @Nonnull ForumChannel channel, @Nonnull ForumTag tag, @Nullable EmojiUnion previous) + { + super(api, responseNumber, channel, tag, previous, tag.getEmoji(), IDENTIFIER); + } + + /** + * The old {@link EmojiUnion} for the {@link ForumTag} + * + * @return The old {@link EmojiUnion} + */ + @Nullable + public EmojiUnion getOldEmoji() + { + return getOldValue(); + } + + /** + * The new {@link EmojiUnion} for the {@link ForumTag} + * + * @return The new {@link EmojiUnion} + */ + @Nullable + public EmojiUnion getNewEmoji() + { + return getNewValue(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateModeratedEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateModeratedEvent.java new file mode 100644 index 0000000000..8bffa3fa0f --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateModeratedEvent.java @@ -0,0 +1,58 @@ +/* + * 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.api.events.channel.forum.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * Indicates that the {@link ForumTag#isModerated() moderated status} of a {@link ForumTag} changed. + * + *

    Requirements
    + * This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * {@link net.dv8tion.jda.api.JDABuilder#createLight(String, Collection) JDABuilder.createLight(...)} disables this by default. + * + *

    Identifier: {@code moderated} + */ +@SuppressWarnings("ConstantConditions") +public class ForumTagUpdateModeratedEvent extends GenericForumTagUpdateEvent +{ + public static final String IDENTIFIER = "moderated"; + + public ForumTagUpdateModeratedEvent(@Nonnull JDA api, long responseNumber, @Nonnull ForumChannel channel, @Nonnull ForumTag tag, boolean previous) + { + super(api, responseNumber, channel, tag, previous, tag.isModerated(), IDENTIFIER); + } + + @Nonnull + @Override + public Boolean getOldValue() + { + return super.getOldValue(); + } + + @Nonnull + @Override + public Boolean getNewValue() + { + return super.getNewValue(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateNameEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateNameEvent.java new file mode 100644 index 0000000000..3ca2270282 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/ForumTagUpdateNameEvent.java @@ -0,0 +1,70 @@ +/* + * 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.api.events.channel.forum.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * Indicates that the {@link ForumTag#getName() name} of a {@link ForumTag} changed. + * + *

    Requirements
    + * This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * {@link net.dv8tion.jda.api.JDABuilder#createLight(String, Collection) JDABuilder.createLight(...)} disables this by default. + * + *

    Identifier: {@code name} + */ +@SuppressWarnings("ConstantConditions") +public class ForumTagUpdateNameEvent extends GenericForumTagUpdateEvent +{ + public static final String IDENTIFIER = "name"; + + public ForumTagUpdateNameEvent(@Nonnull JDA api, long responseNumber, @Nonnull ForumChannel channel, @Nonnull ForumTag tag, @Nonnull String previous) + { + super(api, responseNumber, channel, tag, previous, tag.getName(), IDENTIFIER); + } + + @Nonnull + public String getOldName() + { + return getOldValue(); + } + + @Nonnull + public String getNewName() + { + return getNewValue(); + } + + @Nonnull + @Override + public String getOldValue() + { + return super.getOldValue(); + } + + @Nonnull + @Override + public String getNewValue() + { + return super.getNewValue(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/GenericForumTagUpdateEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/GenericForumTagUpdateEvent.java new file mode 100644 index 0000000000..0c78285a28 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/forum/update/GenericForumTagUpdateEvent.java @@ -0,0 +1,84 @@ +/* + * 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.api.events.channel.forum.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; +import net.dv8tion.jda.api.events.UpdateEvent; +import net.dv8tion.jda.api.events.channel.forum.GenericForumTagEvent; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * Abstraction of all {@link ForumTag} updates. + * + *

    Requirements
    + * This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * {@link net.dv8tion.jda.api.JDABuilder#createLight(String, Collection) JDABuilder.createLight(...)} disables this by default. + * + * @param + * The type of the updated field + */ +public abstract class GenericForumTagUpdateEvent extends GenericForumTagEvent implements UpdateEvent +{ + private final T previous; + private final T next; + private final String identifier; + + public GenericForumTagUpdateEvent(@Nonnull JDA api, long responseNumber, @Nonnull ForumChannel channel, @Nonnull ForumTag tag, + T previous, T next, @Nonnull String identifier) + { + super(api, responseNumber, channel, tag); + this.previous = previous; + this.next = next; + this.identifier = identifier; + } + + @Nonnull + @Override + public ForumTag getEntity() + { + return getTag(); + } + + @Override + public T getOldValue() + { + return previous; + } + + @Override + public T getNewValue() + { + return next; + } + + @Nonnull + @Override + public String getPropertyIdentifier() + { + return identifier; + } + + @Override + public String toString() + { + return "ForumTagUpdate[" + getPropertyIdentifier() + "](" + getOldValue() + "->" + getNewValue() + ')'; + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateAppliedTagsEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateAppliedTagsEvent.java new file mode 100644 index 0000000000..0d778fabc3 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateAppliedTagsEvent.java @@ -0,0 +1,122 @@ +/* + * 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.api.events.channel.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.ChannelField; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; +import net.dv8tion.jda.api.utils.cache.SortedSnowflakeCacheView; +import net.dv8tion.jda.internal.utils.Helpers; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Indicates that the tags applied to a {@link ThreadChannel forum post thread} have been updated. + * + * @see ChannelField#APPLIED_TAGS + */ +public class ChannelUpdateAppliedTagsEvent extends GenericChannelUpdateEvent> +{ + public ChannelUpdateAppliedTagsEvent(@Nonnull JDA api, long responseNumber, @Nonnull ThreadChannel channel, @Nonnull List oldValue, @Nonnull List newValue) + { + super(api, responseNumber, channel, ChannelField.APPLIED_TAGS, oldValue, newValue); + } + + /** + * The newly added tags. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @return The tags that were added to the post + */ + @Nonnull + public List getAddedTags() + { + List newTags = new ArrayList<>(getNewTags()); + newTags.removeAll(getOldTags()); + return newTags; + } + + /** + * The removed tags. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @return The tags that were removed from the post + */ + @Nonnull + public List getRemovedTags() + { + List oldTags = new ArrayList<>(getOldTags()); + oldTags.removeAll(getNewTags()); + return oldTags; + } + + /** + * The new list of applied tags. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @return The updated list of applied tags + */ + @Nonnull + public List getNewTags() + { + SortedSnowflakeCacheView cache = getChannel().asThreadChannel().getParentChannel().asForumChannel().getAvailableTagCache(); + return getNewValue().stream() + .map(cache::getElementById) + .filter(Objects::nonNull) + .sorted() + .collect(Helpers.toUnmodifiableList()); + } + + /** + * The old list of applied tags. + * + *

    This requires {@link net.dv8tion.jda.api.utils.cache.CacheFlag#FORUM_TAGS CacheFlag.FORUM_TAGS} to be enabled. + * + * @return The previous list of applied tags + */ + @Nonnull + public List getOldTags() + { + SortedSnowflakeCacheView cache = getChannel().asThreadChannel().getParentChannel().asForumChannel().getAvailableTagCache(); + return getOldValue().stream() + .map(cache::getElementById) + .filter(Objects::nonNull) + .sorted() + .collect(Helpers.toUnmodifiableList()); + } + + @Nonnull + @Override + public List getOldValue() + { + return super.getOldValue(); + } + + @Nonnull + @Override + public List getNewValue() + { + return super.getNewValue(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultReactionEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultReactionEvent.java new file mode 100644 index 0000000000..36ec2733d2 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultReactionEvent.java @@ -0,0 +1,41 @@ +/* + * 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.api.events.channel.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.ChannelField; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Indicates that the {@link ForumChannel#getDefaultReaction() default reaction emoji} of a {@link ForumChannel} changed. + * + *

    Can be used to retrieve the old default reaction and the new one. + * + * @see ChannelField#DEFAULT_REACTION_EMOJI + */ +public class ChannelUpdateDefaultReactionEvent extends GenericChannelUpdateEvent +{ + public ChannelUpdateDefaultReactionEvent(@Nonnull JDA api, long responseNumber, @Nonnull Channel channel, @Nullable EmojiUnion oldValue, @Nullable EmojiUnion newValue) + { + super(api, responseNumber, channel, ChannelField.DEFAULT_REACTION_EMOJI, oldValue, newValue); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultSortOrderEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultSortOrderEvent.java new file mode 100644 index 0000000000..35a0457dd7 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultSortOrderEvent.java @@ -0,0 +1,54 @@ +/* + * 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.api.events.channel.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.ChannelField; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; + +import javax.annotation.Nonnull; + +/** + * Indicates that the {@link ForumChannel#getDefaultSortOrder() default sort order} of a {@link ForumChannel} changed. + * + *

    Can be used to retrieve the old default sort order and the new one. + * + * @see ChannelField#DEFAULT_SORT_ORDER + */ +@SuppressWarnings("ConstantConditions") +public class ChannelUpdateDefaultSortOrderEvent extends GenericChannelUpdateEvent +{ + public ChannelUpdateDefaultSortOrderEvent(@Nonnull JDA api, long responseNumber, @Nonnull Channel channel, @Nonnull ForumChannel.SortOrder oldValue, @Nonnull ForumChannel.SortOrder newValue) + { + super(api, responseNumber, channel, ChannelField.DEFAULT_SORT_ORDER, oldValue, newValue); + } + + @Nonnull + @Override + public ForumChannel.SortOrder getOldValue() + { + return super.getOldValue(); + } + + @Nonnull + @Override + public ForumChannel.SortOrder getNewValue() + { + return super.getNewValue(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultThreadSlowmodeEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultThreadSlowmodeEvent.java new file mode 100644 index 0000000000..0d3a55b5a0 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateDefaultThreadSlowmodeEvent.java @@ -0,0 +1,53 @@ +/* + * 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.api.events.channel.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.ChannelField; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; + +import javax.annotation.Nonnull; + +/** + * Indicates that the {@link IThreadContainer#getDefaultThreadSlowmode() default thread slowmode} of a {@link IThreadContainer thread container channel} changed. + * + *

    Can be used to retrieve the old default thread slowmode and the new one. + * + * @see ChannelField#DEFAULT_THREAD_SLOWMODE + */ +public class ChannelUpdateDefaultThreadSlowmodeEvent extends GenericChannelUpdateEvent +{ + public ChannelUpdateDefaultThreadSlowmodeEvent(@Nonnull JDA api, long responseNumber, @Nonnull Channel channel, int oldValue, int newValue) + { + super(api, responseNumber, channel, ChannelField.DEFAULT_THREAD_SLOWMODE, oldValue, newValue); + } + + @Nonnull + @Override + public Integer getOldValue() + { + return super.getOldValue(); + } + + @Nonnull + @Override + public Integer getNewValue() + { + return super.getNewValue(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateFlagsEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateFlagsEvent.java new file mode 100644 index 0000000000..9a61976482 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateFlagsEvent.java @@ -0,0 +1,54 @@ +/* + * 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.api.events.channel.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.ChannelField; +import net.dv8tion.jda.api.entities.channel.ChannelFlag; + +import javax.annotation.Nonnull; +import java.util.EnumSet; + +/** + * Indicates that the {@link Channel#getFlags() flags} of a {@link Channel} changed. + * + *

    Can be used to retrieve the old flags and the new ones. + * + * @see ChannelField#FLAGS + */ +public class ChannelUpdateFlagsEvent extends GenericChannelUpdateEvent> +{ + public ChannelUpdateFlagsEvent(@Nonnull JDA api, long responseNumber, @Nonnull Channel channel, @Nonnull EnumSet oldValue, @Nonnull EnumSet newValue) + { + super(api, responseNumber, channel, ChannelField.FLAGS, oldValue, newValue); + } + + @Nonnull + @Override + public EnumSet getOldValue() + { + return super.getOldValue(); + } + + @Nonnull + @Override + public EnumSet getNewValue() + { + return super.getNewValue(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java b/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java index 56028d45b1..6d8c362982 100644 --- a/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java +++ b/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java @@ -19,6 +19,13 @@ import net.dv8tion.jda.api.events.channel.ChannelCreateEvent; import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent; import net.dv8tion.jda.api.events.channel.GenericChannelEvent; +import net.dv8tion.jda.api.events.channel.forum.ForumTagAddEvent; +import net.dv8tion.jda.api.events.channel.forum.ForumTagRemoveEvent; +import net.dv8tion.jda.api.events.channel.forum.GenericForumTagEvent; +import net.dv8tion.jda.api.events.channel.forum.update.ForumTagUpdateEmojiEvent; +import net.dv8tion.jda.api.events.channel.forum.update.ForumTagUpdateModeratedEvent; +import net.dv8tion.jda.api.events.channel.forum.update.ForumTagUpdateNameEvent; +import net.dv8tion.jda.api.events.channel.forum.update.GenericForumTagUpdateEvent; import net.dv8tion.jda.api.events.channel.update.*; import net.dv8tion.jda.api.events.emoji.EmojiAddedEvent; import net.dv8tion.jda.api.events.emoji.EmojiRemovedEvent; @@ -182,11 +189,15 @@ public void onChannelDelete(@Nonnull ChannelDeleteEvent event) {} //Channel Update Events public void onChannelUpdateBitrate(@Nonnull ChannelUpdateBitrateEvent event) {} public void onChannelUpdateName(@Nonnull ChannelUpdateNameEvent event) {} + public void onChannelUpdateFlags(@Nonnull ChannelUpdateFlagsEvent event) {} public void onChannelUpdateNSFW(@Nonnull ChannelUpdateNSFWEvent event) {} public void onChannelUpdateParent(@Nonnull ChannelUpdateParentEvent event) {} public void onChannelUpdatePosition(@Nonnull ChannelUpdatePositionEvent event) {} public void onChannelUpdateRegion(@Nonnull ChannelUpdateRegionEvent event) {} public void onChannelUpdateSlowmode(@Nonnull ChannelUpdateSlowmodeEvent event) {} + public void onChannelUpdateDefaultThreadSlowmode(@Nonnull ChannelUpdateDefaultThreadSlowmodeEvent event) {} + public void onChannelUpdateDefaultReaction(@Nonnull ChannelUpdateDefaultReactionEvent event) {} + public void onChannelUpdateDefaultSortOrder(@Nonnull ChannelUpdateDefaultSortOrderEvent event) {} public void onChannelUpdateTopic(@Nonnull ChannelUpdateTopicEvent event) {} public void onChannelUpdateType(@Nonnull ChannelUpdateTypeEvent event) {} public void onChannelUpdateUserLimit(@Nonnull ChannelUpdateUserLimitEvent event) {} @@ -195,6 +206,14 @@ public void onChannelUpdateArchiveTimestamp(@Nonnull ChannelUpdateArchiveTimesta public void onChannelUpdateAutoArchiveDuration(@Nonnull ChannelUpdateAutoArchiveDurationEvent event) {} public void onChannelUpdateLocked(@Nonnull ChannelUpdateLockedEvent event) {} public void onChannelUpdateInvitable(@Nonnull ChannelUpdateInvitableEvent event) {} + public void onChannelUpdateAppliedTags(@Nonnull ChannelUpdateAppliedTagsEvent event) {} + + //Forum Tag Events + public void onForumTagAdd(@Nonnull ForumTagAddEvent event) {} + public void onForumTagRemove(@Nonnull ForumTagRemoveEvent event) {} + public void onForumTagUpdateName(@Nonnull ForumTagUpdateNameEvent event) {} + public void onForumTagUpdateEmoji(@Nonnull ForumTagUpdateEmojiEvent event) {} + public void onForumTagUpdateModerated(@Nonnull ForumTagUpdateModeratedEvent event) {} //Thread Events public void onThreadRevealed(@Nonnull ThreadRevealedEvent event) {} @@ -344,6 +363,8 @@ public void onGenericEmojiUpdate(@Nonnull GenericEmojiUpdateEvent event) {} public void onGenericGuildSticker(@Nonnull GenericGuildStickerEvent event) {} public void onGenericGuildStickerUpdate(@Nonnull GenericGuildStickerUpdateEvent event) {} public void onGenericPermissionOverride(@Nonnull GenericPermissionOverrideEvent event) {} + public void onGenericForumTag(@Nonnull GenericForumTagEvent event) {} + public void onGenericForumTagUpdate(@Nonnull GenericForumTagUpdateEvent event) {} private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); private static final ConcurrentMap, MethodHandle> methods = new ConcurrentHashMap<>(); diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/ChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/ChannelManager.java index 99988effcd..4ddc5493ac 100644 --- a/src/main/java/net/dv8tion/jda/api/managers/channel/ChannelManager.java +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/ChannelManager.java @@ -17,6 +17,7 @@ package net.dv8tion.jda.api.managers.channel; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.managers.Manager; @@ -45,35 +46,49 @@ public interface ChannelManager> extends Manager { /** Used to reset the name field */ - long NAME = 1; + long NAME = 1; /** Used to reset the parent field */ - long PARENT = 1 << 1; + long PARENT = 1 << 1; /** Used to reset the topic field */ - long TOPIC = 1 << 2; + long TOPIC = 1 << 2; /** Used to reset the position field */ - long POSITION = 1 << 3; + long POSITION = 1 << 3; /** Used to reset the nsfw field */ - long NSFW = 1 << 4; + long NSFW = 1 << 4; /** Used to reset the userlimit field */ - long USERLIMIT = 1 << 5; + long USERLIMIT = 1 << 5; /** Used to reset the bitrate field */ - long BITRATE = 1 << 6; + long BITRATE = 1 << 6; /** Used to reset the permission field */ - long PERMISSION = 1 << 7; + long PERMISSION = 1 << 7; /** Used to reset the rate-limit per user field */ - long SLOWMODE = 1 << 8; + long SLOWMODE = 1 << 8; /** Used to reset the channel type field */ - long TYPE = 1 << 9; + long TYPE = 1 << 9; /** Used to reset the region field */ - long REGION = 1 << 10; + long REGION = 1 << 10; /** Used to reset the auto-archive-duration field */ long AUTO_ARCHIVE_DURATION = 1 << 11; /** Used to reset the archived field */ - long ARCHIVED = 1 << 12; + long ARCHIVED = 1 << 12; /** Used to reset the locked field */ - long LOCKED = 1 << 13; + long LOCKED = 1 << 13; /** Used to reset the invitable field */ - long INVITEABLE = 1 << 14; + long INVITEABLE = 1 << 14; + /** Used to reset the available tags field */ + long AVAILABLE_TAGS = 1 << 15; + /** Used to reset the applied tags field */ + long APPLIED_TAGS = 1 << 16; + /** + * Used to reset the pinned state field + */ + long PINNED = 1 << 17; + /** + * Used to reset the require tag state field + */ + long REQUIRE_TAG = 1 << 18; + /** Used to reset the default reaction emoji field */ + long DEFAULT_REACTION = 1 << 19; /** * Resets the fields specified by the provided bit-flag pattern. @@ -93,6 +108,15 @@ public interface ChannelManager{@link #PERMISSION} *

  • {@link #TYPE}
  • *
  • {@link #REGION}
  • + *
  • {@link #AUTO_ARCHIVE_DURATION}
  • + *
  • {@link #ARCHIVED}
  • + *
  • {@link #LOCKED}
  • + *
  • {@link #INVITEABLE}
  • + *
  • {@link #AVAILABLE_TAGS}
  • + *
  • {@link #APPLIED_TAGS}
  • + *
  • {@link #PINNED}
  • + *
  • {@link #REQUIRE_TAG}
  • + *
  • {@link #DEFAULT_REACTION}
  • * * * @param fields @@ -120,6 +144,15 @@ public interface ChannelManager{@link #PERMISSION} *
  • {@link #TYPE}
  • *
  • {@link #REGION}
  • + *
  • {@link #AUTO_ARCHIVE_DURATION}
  • + *
  • {@link #ARCHIVED}
  • + *
  • {@link #LOCKED}
  • + *
  • {@link #INVITEABLE}
  • + *
  • {@link #AVAILABLE_TAGS}
  • + *
  • {@link #APPLIED_TAGS}
  • + *
  • {@link #PINNED}
  • + *
  • {@link #REQUIRE_TAG}
  • + *
  • {@link #DEFAULT_REACTION}
  • * * * @param fields @@ -156,7 +189,7 @@ default Guild getGuild() /** * Sets the name of the selected {@link GuildChannel GuildChannel}. * - *

    A channel name must not be {@code null} nor empty or more than 100 characters long! + *

    A channel name must not be {@code null} nor empty or more than {@value Channel#MAX_NAME_LENGTH} characters long! *
    TextChannel names may only be populated with alphanumeric (with underscore and dash). * *

    Example: {@code mod-only} or {@code generic_name} @@ -166,7 +199,7 @@ default Guild getGuild() * The new name for the selected {@link GuildChannel GuildChannel} * * @throws IllegalArgumentException - * If the provided name is {@code null} or not between 1-100 characters long + * If the provided name is {@code null} or not between 1-{@value Channel#MAX_NAME_LENGTH} characters long * * @return ChannelManager for chaining convenience */ diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/attribute/ISlowmodeChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/attribute/ISlowmodeChannelManager.java new file mode 100644 index 0000000000..137d1553e3 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/attribute/ISlowmodeChannelManager.java @@ -0,0 +1,66 @@ +/* + * 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.api.managers.channel.attribute; + +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; +import net.dv8tion.jda.api.managers.channel.ChannelManager; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; + +/** + * Manager which supports setting slowmode of a channel. + * + * @param + * The concrete {@link ISlowmodeChannel} type + * @param + * The concrete manager type + */ +public interface ISlowmodeChannelManager> + extends ChannelManager +{ + /** + * Sets the slowmode of the selected channel. + *
    Provide {@code 0} to disable slowmode. + * + *

    A channel slowmode must not be negative nor greater than {@link ISlowmodeChannel#MAX_SLOWMODE}! + * + *

    Note: Bots are unaffected by this. + *
    Having {@link Permission#MESSAGE_MANAGE MESSAGE_MANAGE} or + * {@link Permission#MANAGE_CHANNEL MANAGE_CHANNEL} permission also + * grants immunity to slowmode. + * + *

    Special case
    + * {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannels} use this to limit how many posts a user can create. + * The client refers to this as the post slowmode. + * + * + * @param slowmode + * The new slowmode + * + * @throws IllegalArgumentException + * If the provided slowmode is negative or greater than {@value ISlowmodeChannel#MAX_SLOWMODE} + * + * @return ChannelManager for chaining convenience + * + * @see net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel#getSlowmode() + */ + @Nonnull + @CheckReturnValue + M setSlowmode(int slowmode); +} diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/CategoryManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/CategoryManager.java index be36dc80cd..53ef66a487 100644 --- a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/CategoryManager.java +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/CategoryManager.java @@ -20,6 +20,14 @@ import net.dv8tion.jda.api.managers.channel.attribute.IPermissionContainerManager; import net.dv8tion.jda.api.managers.channel.attribute.IPositionableChannelManager; +/** + * Manager providing methods to modify a {@link Category}. + * + *

    Example + *

    {@code
    + * manager.setName("Cool People Only").queue();
    + * }
    + */ public interface CategoryManager extends IPermissionContainerManager, IPositionableChannelManager diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/ForumChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/ForumChannelManager.java new file mode 100644 index 0000000000..2ae1348633 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/ForumChannelManager.java @@ -0,0 +1,111 @@ +/* + * 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.api.managers.channel.concrete; + +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.BaseForumTag; +import net.dv8tion.jda.api.entities.channel.forums.ForumTagData; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.managers.channel.attribute.IAgeRestrictedChannelManager; +import net.dv8tion.jda.api.managers.channel.attribute.ISlowmodeChannelManager; +import net.dv8tion.jda.api.managers.channel.middleman.StandardGuildChannelManager; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +/** + * Manager providing functionality to modify a {@link ForumChannel ForumChannel}. + * + *

    Example + *

    {@code
    + * manager.setName("gamer-forum")
    + *  .setSlowmode(10)
    + *  .setTopic("Welcome to the gamer forum!")
    + *  .queue();
    + * manager.reset(ChannelManager.NSFW | ChannelManager.NAME)
    + *  .setName("gamer-forum-nsfw")
    + *  .setNSFW(true)
    + *  .queue();
    + * }
    + */ +public interface ForumChannelManager extends + StandardGuildChannelManager, + IAgeRestrictedChannelManager, + ISlowmodeChannelManager +{ + /** + * Sets the tag requirement state of this {@link ForumChannel}. + *
    If true, all new posts must have at least one tag. + * + * @param requireTag + * The new tag requirement state for the selected {@link ForumChannel} + * + * @return ChannelManager for chaining convenience. + * + * @see ForumChannel#isTagRequired() + */ + @Nonnull + @CheckReturnValue + ForumChannelManager setTagRequired(boolean requireTag); + + /** + * Sets the available tags of the selected {@link ForumChannel}. + *
    Tags will be ordered based on the provided list order. + * + *

    This is a full replacement of the tags list, all missing tags will be removed. + * You can use {@link ForumTagData} to create new tags or update existing ones. + * + *

    Example + *

    {@code
    +     * List tags = new ArrayList<>(channel.getAvailableTags());
    +     * tags.add(new ForumTagData("question").setModerated(true)); // add a new tag
    +     * tags.set(0, ForumTagData.from(tags.get(0)).setName("bug report")); // update an existing tag
    +     * // Update the tag list
    +     * channel.getManager().setAvailableTags(tags).queue();
    +     * }
    + * + * @param tags + * The new available tags in the desired order. + * + * @throws IllegalArgumentException + * If the provided list is null or contains null elements + * + * @return ChannelManager for chaining convenience + * + * @see ForumChannel#getAvailableTags() + */ + @Nonnull + @CheckReturnValue + ForumChannelManager setAvailableTags(@Nonnull List tags); + + /** + * Sets the default reaction emoji of the selected {@link ForumChannel}. + *
    This does not support custom emoji from other guilds. + * + * @param emoji + * The new default reaction emoji, or null to unset. + * + * @return ChannelManager for chaining convenience + * + * @see ForumChannel#getDefaultReaction() + */ + @Nonnull + @CheckReturnValue + ForumChannelManager setDefaultReaction(@Nullable Emoji emoji); +} diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/NewsChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/NewsChannelManager.java index 2af8a5373c..286408d4f8 100644 --- a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/NewsChannelManager.java +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/NewsChannelManager.java @@ -25,6 +25,16 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; +/** + * Manager providing methods to modify a {@link NewsChannel}. + * + *

    Example + *

    {@code
    + * manager.setName("no-more-news")
    + *        .setType(ChannelType.TEXT) // Changes channel type to TextChannel
    + *        .queue();
    + * }
    + */ public interface NewsChannelManager extends StandardGuildMessageChannelManager { /** diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/StageChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/StageChannelManager.java index 4ffa251d3d..23a5c3c28e 100644 --- a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/StageChannelManager.java +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/StageChannelManager.java @@ -20,7 +20,16 @@ import net.dv8tion.jda.api.managers.channel.middleman.AudioChannelManager; import net.dv8tion.jda.api.managers.channel.middleman.StandardGuildChannelManager; -//TODO-v5: Docs +/** + * Manager providing methods to modify a {@link StageChannel}. + * + *

    Example + *

    {@code
    + * manager.setName("School Presentations")
    + *        .setBitrate(96000)
    + *        .queue();
    + * }
    + */ public interface StageChannelManager extends AudioChannelManager, StandardGuildChannelManager diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/TextChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/TextChannelManager.java index 2ff48e0796..48205f16e2 100644 --- a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/TextChannelManager.java +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/TextChannelManager.java @@ -16,10 +16,10 @@ package net.dv8tion.jda.api.managers.channel.concrete; -import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.managers.channel.attribute.ISlowmodeChannelManager; import net.dv8tion.jda.api.managers.channel.middleman.StandardGuildMessageChannelManager; import javax.annotation.CheckReturnValue; @@ -40,33 +40,8 @@ * * @see net.dv8tion.jda.api.entities.channel.concrete.TextChannel#getManager() */ -public interface TextChannelManager extends StandardGuildMessageChannelManager +public interface TextChannelManager extends StandardGuildMessageChannelManager, ISlowmodeChannelManager { - /** - * Sets the slowmode of the selected {@link TextChannel TextChannel}. - *
    Provide {@code 0} to reset the slowmode of the {@link TextChannel TextChannel} - * - *

    A channel slowmode must not be negative nor greater than {@link TextChannel#MAX_SLOWMODE TextChannel.MAX_SLOWMODE}! - * - *

    Note: Bots are unaffected by this. - *
    Having {@link Permission#MESSAGE_MANAGE MESSAGE_MANAGE} or - * {@link Permission#MANAGE_CHANNEL MANAGE_CHANNEL} permission also - * grants immunity to slowmode. - * - * @see net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel#getSlowmode() - * - * @param slowmode - * The new slowmode for the selected {@link TextChannel TextChannel} - * - * @throws IllegalArgumentException - * If the provided slowmode is negative or greater than {@link TextChannel#MAX_SLOWMODE TextChannel.MAX_SLOWMODE} - * - * @return ChannelManager for chaining convenience - */ - @Nonnull - @CheckReturnValue - TextChannelManager setSlowmode(int slowmode); - /** * Converts the selected channel to a different {@link ChannelType}. * diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/ThreadChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/ThreadChannelManager.java index fc56d6e02c..ed309ee3d3 100644 --- a/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/ThreadChannelManager.java +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/concrete/ThreadChannelManager.java @@ -16,8 +16,17 @@ package net.dv8tion.jda.api.managers.channel.concrete; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTagSnowflake; import net.dv8tion.jda.api.managers.channel.ChannelManager; +import net.dv8tion.jda.api.managers.channel.attribute.ISlowmodeChannelManager; +import net.dv8tion.jda.internal.utils.Checks; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Collection; /** * Manager providing functionality common for all {@link ThreadChannel ThreadChannels}. @@ -37,32 +46,8 @@ * @see ThreadChannel#getManager() * @see ThreadChannel */ -public interface ThreadChannelManager extends ChannelManager +public interface ThreadChannelManager extends ChannelManager, ISlowmodeChannelManager { - - /** - * Sets the slowmode of the selected {@link ThreadChannel}. - *
    Provide {@code 0} to reset the slowmode of the {@link ThreadChannel}. - * - *

    A channel slowmode must not be negative nor greater than {@link net.dv8tion.jda.api.entities.channel.concrete.TextChannel#MAX_SLOWMODE TextChannel.MAX_SLOWMODE}! - * - *

    Note: Bots are unaffected by this. - *
    Having {@link net.dv8tion.jda.api.Permission#MESSAGE_MANAGE MESSAGE_MANAGE} or - * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} permission also - * grants immunity to slowmode. - * - * @param slowmode - * The new slowmode, in seconds, for the selected {@link ThreadChannel} - * - * @return this ThreadChannelManager for chaining convenience - * - * @throws IllegalArgumentException - * If the provided slowmode is negative or greater than {@link net.dv8tion.jda.api.entities.channel.concrete.TextChannel#MAX_SLOWMODE TextChannel.MAX_SLOWMODE} - * - * @see ThreadChannel#getSlowmode() - */ - ThreadChannelManager setSlowmode(int slowmode); - /** * Sets the inactive time before autoarchiving of this ThreadChannel. * @@ -75,7 +60,9 @@ public interface ThreadChannelManager extends ChannelManagerThis property can only be set on forum post threads. + * + * @param pinned + * The new pinned state for the selected {@link ThreadChannel} + * + * @throws IllegalStateException + * If the selected {@link ThreadChannel} is not a forum post thread + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the currently logged in account is not the thread owner or does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_THREADS MANAGE_THREADS} permission. + * + * @return this ThreadChannelManager for chaining convenience. + * + * @see ThreadChannel#isPinned() + */ + @Nonnull + @CheckReturnValue + ThreadChannelManager setPinned(boolean pinned); + + /** + * Sets the applied {@link net.dv8tion.jda.api.entities.channel.forums.ForumTag ForumTags} for this forum post thread. + *
    This is only applicable to public threads inside forum channels. The tags must be from the forum channel. + * You can get the list of available tags with {@link ForumChannel#getAvailableTags()}. + * + * @param tags + * The new tags for the thread + * + * @throws IllegalStateException + * If the thread is not a forum post + * @throws IllegalArgumentException + *

      + *
    • If null is provided
    • + *
    • If more than {@value ForumChannel#MAX_POST_TAGS} tags are provided
    • + *
    • If at least one tag is {@link ForumChannel#isTagRequired() required} and none were provided
    • + *
    + * + * @return this ThreadChannelManager for chaining convenience. + */ + @Nonnull + @CheckReturnValue + ThreadChannelManager setAppliedTags(@Nonnull Collection tags); + + /** + * Sets the applied {@link net.dv8tion.jda.api.entities.channel.forums.ForumTag ForumTags} for this forum post thread. + *
    This is only applicable to public threads inside forum channels. The tags must be from the forum channel. + * You can get the list of available tags with {@link ForumChannel#getAvailableTags()}. + * + * @param tags + * The new tags for the thread + * + * @throws IllegalStateException + * If the thread is not a forum post + * @throws IllegalArgumentException + *
      + *
    • If null is provided
    • + *
    • If more than {@value ForumChannel#MAX_POST_TAGS} tags are provided
    • + *
    • If at least one tag is {@link ForumChannel#isTagRequired() required} and none were provided
    • + *
    + * + * @return this ThreadChannelManager for chaining convenience. + */ + @Nonnull + @CheckReturnValue + default ThreadChannelManager setAppliedTags(@Nonnull ForumTagSnowflake... tags) + { + Checks.noneNull(tags, "Tags"); + return setAppliedTags(Arrays.asList(tags)); + } } diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/middleman/StandardGuildMessageChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/middleman/StandardGuildMessageChannelManager.java index 94b792a86f..4c283e357a 100644 --- a/src/main/java/net/dv8tion/jda/api/managers/channel/middleman/StandardGuildMessageChannelManager.java +++ b/src/main/java/net/dv8tion/jda/api/managers/channel/middleman/StandardGuildMessageChannelManager.java @@ -45,14 +45,14 @@ public interface StandardGuildMessageChannelManagertopic of the selected {@link StandardGuildMessageChannel channel}. * - *

    A channel topic must not be more than {@code 1024} characters long! - * * @param topic * The new topic for the selected channel, * {@code null} or empty String to reset * * @throws IllegalArgumentException - * If the provided topic is greater than {@code 1024} in length + * If the provided topic is greater than {@value StandardGuildMessageChannel#MAX_TOPIC_LENGTH} in length. + * For {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannels}, + * this limit is {@value net.dv8tion.jda.api.entities.channel.concrete.ForumChannel#MAX_FORUM_TOPIC_LENGTH} instead. * * @return ChannelManager for chaining convenience */ diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/AbstractThreadCreateAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/AbstractThreadCreateAction.java new file mode 100644 index 0000000000..6c45d16fa1 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/AbstractThreadCreateAction.java @@ -0,0 +1,87 @@ +/* + * 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.api.requests.restaction; + +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.requests.RestAction; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; +import java.util.function.Consumer; + +/** + * Common features of all {@link RestAction RestActions} that create a new thread. + * + * @param + * The success type given to the {@link #queue(Consumer, Consumer)} success consumer + * @param + * The common return type of setters, allowing for fluid interface design + */ +public interface AbstractThreadCreateAction> extends RestAction +{ + /** + * The guild to create this {@link GuildChannel} for. + * + * @return The guild + */ + @Nonnull + Guild getGuild(); + + /** + * The {@link ChannelType} for the resulting channel. + * + * @return The channel type + */ + @Nonnull + ChannelType getType(); + + /** + * Sets the name for the new GuildChannel. + * + * @param name + * The not-null name for the new GuildChannel (up to {@value Channel#MAX_NAME_LENGTH} characters) + * + * @throws IllegalArgumentException + * If the provided name is null, empty, or longer than {@value Channel#MAX_NAME_LENGTH} characters + * + * @return The current action, for chaining convenience + */ + @Nonnull + @CheckReturnValue + R setName(@Nonnull String name); + + /** + * Sets the {@link ThreadChannel.AutoArchiveDuration} for the new thread. + *
    This is primarily used to hide threads after the provided time of inactivity. + * Threads are automatically archived after 7 days of inactivity regardless. + * + * @param autoArchiveDuration + * The new archive inactivity duration (which hides the thread) + * + * @throws IllegalArgumentException + * If the provided duration is null + * + * @return The current action, for chaining convenience + */ + @Nonnull + @CheckReturnValue + R setAutoArchiveDuration(@Nonnull ThreadChannel.AutoArchiveDuration autoArchiveDuration); +} diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/AuditableRestAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/AuditableRestAction.java index ba6d7b8b37..56861c48dc 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/restaction/AuditableRestAction.java +++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/AuditableRestAction.java @@ -80,12 +80,18 @@ public interface AuditableRestAction extends RestAction */ @Nonnull @Override - AuditableRestAction timeout(long timeout, @Nonnull TimeUnit unit); + default AuditableRestAction timeout(long timeout, @Nonnull TimeUnit unit) + { + return (AuditableRestAction) RestAction.super.timeout(timeout, unit); + } /** * {@inheritDoc} */ @Nonnull @Override - AuditableRestAction deadline(long timestamp); + default AuditableRestAction deadline(long timestamp) + { + return (AuditableRestAction) RestAction.super.deadline(timestamp); + } } diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/ChannelAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/ChannelAction.java index f2d869679c..727d46b4e6 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/restaction/ChannelAction.java +++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/ChannelAction.java @@ -22,10 +22,16 @@ import net.dv8tion.jda.api.entities.IPermissionHolder; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; import net.dv8tion.jda.api.entities.channel.concrete.Category; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.BaseForumTag; +import net.dv8tion.jda.api.entities.channel.forums.ForumTagData; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel; +import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.utils.MiscUtil; import net.dv8tion.jda.internal.utils.Checks; @@ -33,8 +39,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Collection; -import java.util.concurrent.TimeUnit; -import java.util.function.BooleanSupplier; +import java.util.List; /** * Extension of {@link net.dv8tion.jda.api.requests.RestAction RestAction} specifically @@ -55,20 +60,8 @@ * @param * The type of channel to create */ -public interface ChannelAction extends AuditableRestAction +public interface ChannelAction extends FluentAuditableRestAction> { - @Nonnull - @Override - ChannelAction setCheck(@Nullable BooleanSupplier checks); - - @Nonnull - @Override - ChannelAction timeout(long timeout, @Nonnull TimeUnit unit); - - @Nonnull - @Override - ChannelAction deadline(long timestamp); - /** * The guild to create this {@link GuildChannel} in * @@ -89,10 +82,10 @@ public interface ChannelAction extends AuditableRestActi * Sets the name for the new GuildChannel * * @param name - * The not-null name for the new GuildChannel (1-100 chars long) + * The not-null name for the new GuildChannel (1-{@value Channel#MAX_NAME_LENGTH} characters long) * * @throws java.lang.IllegalArgumentException - * If the provided name is null or not between 1-100 chars long + * If the provided name is null or not between 1-{@value Channel#MAX_NAME_LENGTH} characters long * * @return The current ChannelAction, for chaining convenience */ @@ -149,12 +142,14 @@ public interface ChannelAction extends AuditableRestActi * Sets the topic for the new TextChannel * * @param topic - * The topic for the new GuildChannel (max 1024 chars) + * The topic for the new GuildChannel * * @throws UnsupportedOperationException * If this ChannelAction is not for a TextChannel * @throws IllegalArgumentException - * If the provided topic is longer than 1024 chars + * If the provided topic is greater than {@value StandardGuildMessageChannel#MAX_TOPIC_LENGTH} in length. + * For {@link net.dv8tion.jda.api.entities.channel.concrete.ForumChannel ForumChannels}, + * this limit is {@value net.dv8tion.jda.api.entities.channel.concrete.ForumChannel#MAX_FORUM_TOPIC_LENGTH} instead. * * @return The current ChannelAction, for chaining convenience */ @@ -179,7 +174,7 @@ public interface ChannelAction extends AuditableRestActi /** * Sets the slowmode value, which limits the amount of time that individual users must wait - * between sending messages in the new TextChannel. This is measured in seconds. + * between sending messages in the new channel. This is measured in seconds. * *

    Note: Bots are unaffected by this. *
    Having {@link net.dv8tion.jda.api.Permission#MESSAGE_MANAGE MESSAGE_MANAGE} or @@ -190,9 +185,9 @@ public interface ChannelAction extends AuditableRestActi * The number of seconds required to wait between sending messages in the channel. * * @throws UnsupportedOperationException - * If this ChannelAction is not for a TextChannel + * If this ChannelAction is not for a {@link ISlowmodeChannel} * @throws IllegalArgumentException - * If the {@code slowmode} is greater than {@link TextChannel#MAX_SLOWMODE TextChannel.MAX_SLOWMODE}, or less than 0 + * If the {@code slowmode} is greater than {@link ISlowmodeChannel#MAX_SLOWMODE ISlowmodeChannel.MAX_SLOWMODE}, or less than 0 * * @return The current ChannelAction, for chaining convenience */ @@ -200,6 +195,41 @@ public interface ChannelAction extends AuditableRestActi @CheckReturnValue ChannelAction setSlowmode(int slowmode); + /** + * Sets the default reaction emoji of the new {@link ForumChannel}. + *
    This does not support custom emoji from other guilds. + * + * @param emoji + * The new default reaction emoji, or null to unset. + * + * @return The current ChannelAction, for chaining convenience + * + * @see ForumChannel#getDefaultReaction() + */ + @Nonnull + @CheckReturnValue + ChannelAction setDefaultReaction(@Nullable Emoji emoji); + + /** + * Sets the available tags of the new {@link ForumChannel}. + *
    Tags will be ordered based on the provided list order. + * + *

    You can use {@link ForumTagData} to create new tags. + * + * @param tags + * The new available tags in the desired order. + * + * @throws IllegalArgumentException + * If the provided list is null or contains null elements + * + * @return The current ChannelAction, for chaining convenience + * + * @see ForumChannel#getAvailableTags() + */ + @Nonnull + @CheckReturnValue + ChannelAction setAvailableTags(@Nonnull List tags); + /** * Adds a new Role or Member {@link net.dv8tion.jda.api.entities.PermissionOverride PermissionOverride} * for the new GuildChannel. diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/FluentAuditableRestAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/FluentAuditableRestAction.java new file mode 100644 index 0000000000..be209b8c9d --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/FluentAuditableRestAction.java @@ -0,0 +1,64 @@ +/* + * 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.api.requests.restaction; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; + +/** + * Interface used to mixin the customization parameters for {@link AuditableRestAction AuditableRestActions}. + *
    This simply fixes the return types to be the concrete implementation instead of the base interface. + * + * @param + * The result type of the AuditableRestAction + * @param + * The concrete AuditableRestAction type used for chaining (fluent interface) + */ +@SuppressWarnings("unchecked") +public interface FluentAuditableRestAction> extends AuditableRestAction +{ + @Nonnull + @Override + R reason(@Nullable String reason); + + @Nonnull + @Override + R setCheck(@Nullable BooleanSupplier checks); + + @Nonnull + @Override + default R addCheck(@Nonnull BooleanSupplier checks) + { + return (R) AuditableRestAction.super.addCheck(checks); + } + + @Nonnull + @Override + default R timeout(long timeout, @Nonnull TimeUnit unit) + { + return (R) AuditableRestAction.super.timeout(timeout, unit); + } + + @Nonnull + @Override + default R deadline(long timestamp) + { + return (R) AuditableRestAction.super.deadline(timestamp); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/ForumPostAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/ForumPostAction.java new file mode 100644 index 0000000000..e381812d36 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/ForumPostAction.java @@ -0,0 +1,89 @@ +/* + * 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.api.requests.restaction; + +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumPost; +import net.dv8tion.jda.api.entities.channel.forums.ForumTagSnowflake; +import net.dv8tion.jda.api.requests.FluentRestAction; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.dv8tion.jda.api.utils.messages.MessageCreateRequest; +import net.dv8tion.jda.internal.utils.Checks; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Collection; + +/** + * Extension of {@link net.dv8tion.jda.api.requests.RestAction RestAction} specifically + * designed to create new Forum Post Threads. + * + *

    On success, this provides a {@link ForumPost} object with the {@link ForumPost#getMessage() starter message} + * and the {@link ForumPost#getThreadChannel() thread channel} of the post. + * + * @see net.dv8tion.jda.api.entities.channel.concrete.ForumChannel#createForumPost(String, MessageCreateData) + */ +public interface ForumPostAction extends AbstractThreadCreateAction, MessageCreateRequest, FluentRestAction +{ + /** + * The {@link ForumChannel} to create the post in + * + * @return The {@link ForumChannel} + */ + @Nonnull + ForumChannel getChannel(); + + /** + * Configures that tags which should be applied to the new post. + *
    Some forums require setting at least one tag. + * + * @param tags + * Up to {@value ForumChannel#MAX_POST_TAGS} tags to apply + * + * @throws IllegalArgumentException + * If null is provided or more than {@value ForumChannel#MAX_POST_TAGS} tags are provided, + * or if at least one is {@link ForumChannel#isTagRequired() required} and none were provided. + * + * @return The current ForumPostAction for chaining convenience + * + * @see ForumTagSnowflake#fromId(long) + */ + @Nonnull + ForumPostAction setTags(@Nonnull Collection tags); + + /** + * Configures that tags which should be applied to the new post. + *
    Some forums require setting at least one tag. + * + * @param tags + * Up to {@value ForumChannel#MAX_POST_TAGS} tags to apply + * + * @throws IllegalArgumentException + * If null is provided or more than {@value ForumChannel#MAX_POST_TAGS} tags are provided, + * or if at least one is {@link ForumChannel#isTagRequired() required} and none were provided. + * + * @return The current ForumPostAction for chaining convenience + * + * @see ForumTagSnowflake#fromId(long) + */ + @Nonnull + default ForumPostAction setTags(@Nonnull ForumTagSnowflake... tags) + { + Checks.noneNull(tags, "Tags"); + return setTags(Arrays.asList(tags)); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/ThreadChannelAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/ThreadChannelAction.java index 2edd79fb26..8c35b00a95 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/restaction/ThreadChannelAction.java +++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/ThreadChannelAction.java @@ -16,17 +16,11 @@ package net.dv8tion.jda.api.requests.restaction; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; -import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.concurrent.TimeUnit; -import java.util.function.BooleanSupplier; /** * Extension of {@link net.dv8tion.jda.api.requests.RestAction RestAction} specifically @@ -39,56 +33,8 @@ * @see net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer#createThreadChannel(String, long) * @see net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer#createThreadChannel(String, String) */ -public interface ThreadChannelAction extends AuditableRestAction +public interface ThreadChannelAction extends AbstractThreadCreateAction, FluentAuditableRestAction { - @Nonnull - @Override - ThreadChannelAction setCheck(@Nullable BooleanSupplier checks); - - @Nonnull - @Override - ThreadChannelAction timeout(long timeout, @Nonnull TimeUnit unit); - - @Nonnull - @Override - ThreadChannelAction deadline(long timestamp); - - /** - * The guild to create this {@link GuildChannel} in - * - * @return The guild - */ - @Nonnull - Guild getGuild(); - - /** - * The {@link ChannelType} for the resulting channel - * - * @return The channel type - */ - @Nonnull - ChannelType getType(); - - /** - * Sets the name for the new GuildChannel - * - * @param name - * The not-null name for the new GuildChannel (1-100 chars long) - * - * @throws IllegalArgumentException - * If the provided name is null or not between 1-100 chars long - * - * @return The current ChannelAction, for chaining convenience - */ - @Nonnull - @CheckReturnValue - ThreadChannelAction setName(@Nonnull String name); - - //TODO-v5: Docs - @Nonnull - @CheckReturnValue - ThreadChannelAction setAutoArchiveDuration(@Nonnull ThreadChannel.AutoArchiveDuration autoArchiveDuration); - //TODO-v5: Docs @Nonnull @CheckReturnValue diff --git a/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java b/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java index 3a13b948f2..d534bf7a92 100644 --- a/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java +++ b/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java @@ -806,6 +806,13 @@ default SnowflakeCacheView getNewsChannelCache() return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getNewsChannelCache)); } + @Nonnull + @Override + default SnowflakeCacheView getForumChannelCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getForumChannelCache)); + } + /** * This returns the {@link net.dv8tion.jda.api.JDA JDA} instance which has the same id as the one provided. *
    If there is no shard with an id that matches the provided one, this will return {@code null}. diff --git a/src/main/java/net/dv8tion/jda/api/utils/cache/CacheFlag.java b/src/main/java/net/dv8tion/jda/api/utils/cache/CacheFlag.java index 436ebfa0bf..d5934739ec 100644 --- a/src/main/java/net/dv8tion/jda/api/utils/cache/CacheFlag.java +++ b/src/main/java/net/dv8tion/jda/api/utils/cache/CacheFlag.java @@ -19,6 +19,8 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.requests.GatewayIntent; import javax.annotation.Nonnull; @@ -70,6 +72,10 @@ public enum CacheFlag * Enables cache for {@link Role#getTags()} */ ROLE_TAGS, + /** + * Enables cache for {@link ForumChannel#getAvailableTagCache()} and {@link ThreadChannel#getAppliedTags()} + */ + FORUM_TAGS, /** * Enables cache for {@link Member#getOnlineStatus()} *
    This is enabled implicitly by {@link #ACTIVITY} and {@link #CLIENT_STATUS}. diff --git a/src/main/java/net/dv8tion/jda/api/utils/cache/CacheView.java b/src/main/java/net/dv8tion/jda/api/utils/cache/CacheView.java index 6a75cac639..ef50e5271b 100644 --- a/src/main/java/net/dv8tion/jda/api/utils/cache/CacheView.java +++ b/src/main/java/net/dv8tion/jda/api/utils/cache/CacheView.java @@ -140,7 +140,6 @@ default void forEachUnordered(@Nonnull final Consumer action) * * @see #acceptStream(Consumer) */ - @Nullable default R applyStream(@Nonnull Function, ? extends R> action) { Checks.notNull(action, "Action"); diff --git a/src/main/java/net/dv8tion/jda/internal/JDAImpl.java b/src/main/java/net/dv8tion/jda/internal/JDAImpl.java index 4330d26b5b..fee8316045 100644 --- a/src/main/java/net/dv8tion/jda/internal/JDAImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/JDAImpl.java @@ -110,6 +110,7 @@ public class JDAImpl implements JDA protected final SnowflakeCacheViewImpl voiceChannelCache = new SnowflakeCacheViewImpl<>(VoiceChannel.class, Channel::getName); protected final SnowflakeCacheViewImpl stageChannelCache = new SnowflakeCacheViewImpl<>(StageChannel.class, Channel::getName); protected final SnowflakeCacheViewImpl threadChannelsCache = new SnowflakeCacheViewImpl<>(ThreadChannel.class, Channel::getName); + protected final SnowflakeCacheViewImpl forumChannelsCache = new SnowflakeCacheViewImpl<>(ForumChannel.class, Channel::getName); protected final SnowflakeCacheViewImpl privateChannelCache = new SnowflakeCacheViewImpl<>(PrivateChannel.class, Channel::getName); protected final LinkedList privateChannelLRU = new LinkedList<>(); @@ -703,6 +704,13 @@ public SnowflakeCacheView getThreadChannelCache() return threadChannelsCache; } + @Nonnull + @Override + public SnowflakeCacheView getForumChannelCache() + { + return forumChannelsCache; + } + @Nonnull @Override public SnowflakeCacheView getPrivateChannelCache() @@ -1160,6 +1168,11 @@ public SnowflakeCacheViewImpl getThreadChannelsView() return threadChannelsCache; } + public SnowflakeCacheViewImpl getForumChannelsView() + { + return forumChannelsCache; + } + public SnowflakeCacheViewImpl getPrivateChannelsView() { return privateChannelCache; 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 f56ee87ed4..94a21e5841 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java @@ -30,8 +30,10 @@ import net.dv8tion.jda.api.entities.Guild.VerificationLevel; import net.dv8tion.jda.api.entities.MessageEmbed.*; import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer; import net.dv8tion.jda.api.entities.channel.concrete.*; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; 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.middleman.GuildMessageChannel; @@ -71,6 +73,7 @@ import net.dv8tion.jda.internal.utils.UnlockHook; import net.dv8tion.jda.internal.utils.cache.MemberCacheViewImpl; import net.dv8tion.jda.internal.utils.cache.SnowflakeCacheViewImpl; +import net.dv8tion.jda.internal.utils.cache.SortedSnowflakeCacheViewImpl; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.map.CaseInsensitiveMap; import org.slf4j.Logger; @@ -85,6 +88,7 @@ import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.StreamSupport; public class EntityBuilder @@ -338,7 +342,17 @@ public GuildImpl createGuild(long guildId, DataObject guildJson, TLongObjectMap< for (int i = 0; i < threadArray.length(); i++) { DataObject threadJson = threadArray.getObject(i); - createThreadChannel(guildObj, threadJson, guildObj.getIdLong()); + try + { + createThreadChannel(guildObj, threadJson, guildObj.getIdLong()); + } + catch (Exception ex) + { + if (MISSING_CHANNEL.equals(ex.getMessage())) + LOG.debug("Discarding thread without cached parent channel. JSON: {}", threadJson); + else + LOG.warn("Failed to create thread channel for guild with id {}.\nJSON: {}", guildId, threadJson, ex); + } } createGuildEmojiPass(guildObj, emojisArray); @@ -375,6 +389,9 @@ private void createGuildChannel(GuildImpl guildObj, DataObject channelData) case CATEGORY: createCategory(guildObj, channelData, guildObj.getIdLong()); break; + case FORUM: + createForumChannel(guildObj, channelData, guildObj.getIdLong()); + break; default: LOG.debug("Cannot create channel for type " + channelData.getInt("type")); } @@ -1009,6 +1026,7 @@ public TextChannel createTextChannel(GuildImpl guildObj, DataObject json, long g .setTopic(json.getString("topic", null)) .setPosition(json.getInt("position")) .setNSFW(json.getBoolean("nsfw")) + .setDefaultThreadSlowmode(json.getInt("default_thread_rate_limit_per_user", 0)) .setSlowmode(json.getInt("rate_limit_per_user", 0)); createOverridesPass(channel, json.getArray("permission_overwrites")); @@ -1150,12 +1168,17 @@ public ThreadChannel createThreadChannel(DataObject json, long guildId) public ThreadChannel createThreadChannel(GuildImpl guild, DataObject json, long guildId) { boolean playbackCache = false; - final long id = json.getLong("id"); + final long id = json.getUnsignedLong("id"); + final long parentId = json.getUnsignedLong("parent_id"); final ChannelType type = ChannelType.fromId(json.getInt("type")); if (guild == null) guild = (GuildImpl) getJDA().getGuildsView().get(guildId); + IThreadContainer parent = guild.getChannelById(IThreadContainer.class, parentId); + if (parent == null) + throw new IllegalArgumentException(MISSING_CHANNEL); + ThreadChannelImpl channel = ((ThreadChannelImpl) getJDA().getThreadChannelsView().get(id)); if (channel == null) { @@ -1174,12 +1197,20 @@ public ThreadChannel createThreadChannel(GuildImpl guild, DataObject json, long DataObject threadMetadata = json.getObject("thread_metadata"); + if (!json.isNull("applied_tags") && api.isCacheFlagSet(CacheFlag.FORUM_TAGS)) + { + DataArray array = json.getArray("applied_tags"); + channel.setAppliedTags(IntStream.range(0, array.length()).mapToLong(array::getUnsignedLong)); + } + channel .setName(json.getString("name")) - .setParentChannelId(json.getLong("parent_id")) + .setFlags(json.getInt("flags", 0)) + .setParentChannel(parent) .setOwnerId(json.getLong("owner_id")) .setMemberCount(json.getInt("member_count")) .setMessageCount(json.getInt("message_count")) + .setTotalMessageCount(json.getInt("total_message_count", 0)) .setLatestMessageIdLong(json.getLong("last_message_id", 0)) .setSlowmode(json.getInt("rate_limit_per_user", 0)) .setLocked(threadMetadata.getBoolean("locked")) @@ -1224,6 +1255,80 @@ public ThreadMember createThreadMember(ThreadChannelImpl threadChannel, Member m return threadMember; } + public ForumChannel createForumChannel(DataObject json, long guildId) + { + return createForumChannel(null, json, guildId); + } + + public ForumChannel createForumChannel(GuildImpl guild, DataObject json, long guildId) + { + boolean playbackCache = false; + final long id = json.getLong("id"); + ForumChannelImpl channel = (ForumChannelImpl) getJDA().getForumChannelsView().get(id); + if (channel == null) + { + if (guild == null) + guild = (GuildImpl) getJDA().getGuildsView().get(guildId); + SnowflakeCacheViewImpl + guildView = guild.getForumChannelsView(), + globalView = getJDA().getForumChannelsView(); + try ( + UnlockHook vlock = guildView.writeLock(); + UnlockHook jlock = globalView.writeLock()) + { + channel = new ForumChannelImpl(id, guild); + guildView.getMap().put(id, channel); + playbackCache = globalView.getMap().put(id, channel) == null; + } + } + + if (api.isCacheFlagSet(CacheFlag.FORUM_TAGS)) + { + DataArray tags = json.getArray("available_tags"); + for (int i = 0; i < tags.length(); i++) + createForumTag(channel, tags.getObject(i), i); + } + + channel + .setParentCategory(json.getLong("parent_id", 0)) + .setFlags(json.getInt("flags", 0)) + .setDefaultReaction(json.optObject("default_reaction_emoji").orElse(null)) + .setDefaultSortOrder(json.getInt("default_sort_order", -1)) + .setName(json.getString("name")) + .setTopic(json.getString("topic", null)) + .setPosition(json.getInt("position")) + .setDefaultThreadSlowmode(json.getInt("default_thread_rate_limit_per_user", 0)) + .setSlowmode(json.getInt("rate_limit_per_user", 0)) + .setNSFW(json.getBoolean("nsfw")); + + createOverridesPass(channel, json.getArray("permission_overwrites")); + if (playbackCache) + getJDA().getEventCache().playbackCache(EventCache.Type.CHANNEL, id); + return channel; + } + + public ForumTagImpl createForumTag(ForumChannelImpl channel, DataObject json, int index) + { + final long id = json.getUnsignedLong("id"); + SortedSnowflakeCacheViewImpl cache = channel.getAvailableTagCache(); + ForumTagImpl tag = (ForumTagImpl) cache.get(id); + + if (tag == null) + { + try (UnlockHook lock = cache.writeLock()) + { + tag = new ForumTagImpl(id); + cache.getMap().put(id, tag); + } + } + + tag.setName(json.getString("name")) + .setModerated(json.getBoolean("moderated")) + .setEmoji(json) + .setPosition(index); + return tag; + } + public PrivateChannel createPrivateChannel(DataObject json) { return createPrivateChannel(json, null); diff --git a/src/main/java/net/dv8tion/jda/internal/entities/ForumChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/ForumChannelImpl.java new file mode 100644 index 0000000000..5a6281d79e --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/entities/ForumChannelImpl.java @@ -0,0 +1,292 @@ +/* + * 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.internal.entities; + +import gnu.trove.map.TLongObjectMap; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.PermissionOverride; +import net.dv8tion.jda.api.entities.channel.ChannelFlag; +import net.dv8tion.jda.api.entities.channel.concrete.Category; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; +import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji; +import net.dv8tion.jda.api.managers.channel.concrete.ForumChannelManager; +import net.dv8tion.jda.api.requests.restaction.ChannelAction; +import net.dv8tion.jda.api.requests.restaction.ForumPostAction; +import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction; +import net.dv8tion.jda.api.utils.MiscUtil; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.dv8tion.jda.internal.entities.channel.middleman.AbstractGuildChannelImpl; +import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IThreadContainerMixin; +import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IWebhookContainerMixin; +import net.dv8tion.jda.internal.entities.channel.mixin.middleman.StandardGuildChannelMixin; +import net.dv8tion.jda.internal.entities.emoji.CustomEmojiImpl; +import net.dv8tion.jda.internal.managers.channel.concrete.ForumChannelManagerImpl; +import net.dv8tion.jda.internal.requests.restaction.ForumPostActionImpl; +import net.dv8tion.jda.internal.utils.Checks; +import net.dv8tion.jda.internal.utils.cache.SortedSnowflakeCacheViewImpl; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.stream.Collectors; + +public class ForumChannelImpl extends AbstractGuildChannelImpl + implements ForumChannel, + GuildChannelUnion, + StandardGuildChannelMixin, + IWebhookContainerMixin, + IThreadContainerMixin +{ + private final TLongObjectMap overrides = MiscUtil.newLongMap(); + private final SortedSnowflakeCacheViewImpl tagCache = new SortedSnowflakeCacheViewImpl<>(ForumTag.class, ForumTag::getName, Comparator.naturalOrder()); + + private Emoji defaultReaction; + private String topic; + private long parentCategoryId; + private boolean nsfw = false; + private int position; + private int flags; + private int slowmode; + private int defaultSortOrder; + protected int defaultThreadSlowmode; + + public ForumChannelImpl(long id, GuildImpl guild) + { + super(id, guild); + } + + @Nonnull + @Override + public ForumChannelManager getManager() + { + return new ForumChannelManagerImpl(this); + } + + @Nonnull + @Override + public List getMembers() + { + return Collections.unmodifiableList(getGuild().getMembers() + .stream() + .filter(m -> m.hasPermission(this, Permission.VIEW_CHANNEL)) + .collect(Collectors.toList())); + } + + @Nonnull + @Override + public ChannelAction createCopy(@Nonnull Guild guild) + { + Checks.notNull(guild, "Guild"); + ChannelAction action = guild.createForumChannel(name) + .setNSFW(nsfw) + .setTopic(topic) + .setSlowmode(slowmode) + .setAvailableTags(getAvailableTags()); + if (defaultReaction instanceof UnicodeEmoji) + action.setDefaultReaction(defaultReaction); + if (guild.equals(getGuild())) + { + Category parent = getParentCategory(); + action.setDefaultReaction(defaultReaction); + if (parent != null) + action.setParent(parent); + for (PermissionOverride o : overrides.valueCollection()) + { + if (o.isMemberOverride()) + action.addMemberPermissionOverride(o.getIdLong(), o.getAllowedRaw(), o.getDeniedRaw()); + else + action.addRolePermissionOverride(o.getIdLong(), o.getAllowedRaw(), o.getDeniedRaw()); + } + } + return action; + } + + @Nonnull + @Override + public EnumSet getFlags() + { + return ChannelFlag.fromRaw(flags); + } + + @Nonnull + @Override + public SortedSnowflakeCacheViewImpl getAvailableTagCache() + { + return tagCache; + } + + @Override + public TLongObjectMap getPermissionOverrideMap() + { + return overrides; + } + + @Override + public boolean isNSFW() + { + return nsfw; + } + + @Override + public int getPositionRaw() + { + return position; + } + + @Override + public long getParentCategoryIdLong() + { + return parentCategoryId; + } + + @Override + public int getSlowmode() + { + return slowmode; + } + + @Override + public String getTopic() + { + return topic; + } + + @Override + public EmojiUnion getDefaultReaction() + { + return (EmojiUnion) defaultReaction; + } + + @Override + public int getDefaultThreadSlowmode() + { + return defaultThreadSlowmode; + } + + @Nonnull + @Override + public SortOrder getDefaultSortOrder() + { + return SortOrder.fromKey(defaultSortOrder); + } + + @Nonnull + @Override + public ForumPostAction createForumPost(@Nonnull String name, @Nonnull MessageCreateData message) + { + checkPermission(Permission.MESSAGE_SEND); + return new ForumPostActionImpl(this, name, new MessageCreateBuilder().applyData(message)); + } + + @Nonnull + @Override + public ThreadChannelAction createThreadChannel(@Nonnull String name) + { + throw new UnsupportedOperationException("You cannot create threads without a message payload in forum channels! Use createForumPost(...) instead."); + } + + @Nonnull + @Override + public ThreadChannelAction createThreadChannel(@Nonnull String name, @Nonnull String messageId) + { + throw new UnsupportedOperationException("You cannot create threads without a message payload in forum channels! Use createForumPost(...) instead."); + } + + public int getRawFlags() + { + return flags; + } + + public int getRawSortOrder() + { + return defaultSortOrder; + } + + // Setters + + @Override + public ForumChannelImpl setParentCategory(long parentCategoryId) + { + this.parentCategoryId = parentCategoryId; + return this; + } + + @Override + public ForumChannelImpl setPosition(int position) + { + this.position = position; + return this; + } + + @Override + public ForumChannelImpl setDefaultThreadSlowmode(int defaultThreadSlowmode) + { + this.defaultThreadSlowmode = defaultThreadSlowmode; + return this; + } + + public ForumChannelImpl setNSFW(boolean nsfw) + { + this.nsfw = nsfw; + return this; + } + + public ForumChannelImpl setSlowmode(int slowmode) + { + this.slowmode = slowmode; + return this; + } + + public ForumChannelImpl setTopic(String topic) + { + this.topic = topic; + return this; + } + + public ForumChannelImpl setFlags(int flags) + { + this.flags = flags; + return this; + } + + public ForumChannelImpl setDefaultReaction(DataObject emoji) + { + if (emoji != null && !emoji.isNull("emoji_id")) + this.defaultReaction = new CustomEmojiImpl("", emoji.getUnsignedLong("emoji_id"), false); + else if (emoji != null && !emoji.isNull("emoji_name")) + this.defaultReaction = Emoji.fromUnicode(emoji.getString("emoji_name")); + else + this.defaultReaction = null; + return this; + } + + public ForumChannelImpl setDefaultSortOrder(int defaultSortOrder) + { + this.defaultSortOrder = defaultSortOrder; + return this; + } +} diff --git a/src/main/java/net/dv8tion/jda/internal/entities/ForumTagImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/ForumTagImpl.java new file mode 100644 index 0000000000..d21f7ce051 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/entities/ForumTagImpl.java @@ -0,0 +1,98 @@ +/* + * 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.internal.entities; + +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.internal.entities.emoji.CustomEmojiImpl; + +import javax.annotation.Nonnull; + +public class ForumTagImpl extends ForumTagSnowflakeImpl implements ForumTag +{ + private boolean moderated; + private String name; + private int position; + private Emoji emoji; + + public ForumTagImpl(long id) + { + super(id); + } + + @Override + public int getPosition() + { + return position; + } + + @Nonnull + @Override + public String getName() + { + return name; + } + + @Override + public boolean isModerated() + { + return moderated; + } + + @Override + public EmojiUnion getEmoji() + { + return (EmojiUnion) emoji; + } + + public ForumTagImpl setModerated(boolean moderated) + { + this.moderated = moderated; + return this; + } + + public ForumTagImpl setName(String name) + { + this.name = name; + return this; + } + + public ForumTagImpl setPosition(int position) + { + this.position = position; + return this; + } + + public ForumTagImpl setEmoji(DataObject json) + { + if (!json.isNull("emoji_id")) + this.emoji = new CustomEmojiImpl("", json.getUnsignedLong("emoji_id"), false); + else if (!json.isNull("emoji_name")) + this.emoji = Emoji.fromUnicode(json.getString("emoji_name")); + else + this.emoji = null; + return this; + } + + @Override + public String toString() + { + return "ForumTag:" + name + "(" + id + ')'; + } +} diff --git a/src/main/java/net/dv8tion/jda/internal/entities/ForumTagSnowflakeImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/ForumTagSnowflakeImpl.java new file mode 100644 index 0000000000..40e7c53e92 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/entities/ForumTagSnowflakeImpl.java @@ -0,0 +1,57 @@ +/* + * 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.internal.entities; + +import net.dv8tion.jda.api.entities.channel.forums.ForumTagSnowflake; + +public class ForumTagSnowflakeImpl implements ForumTagSnowflake +{ + protected final long id; + + public ForumTagSnowflakeImpl(long id) + { + this.id = id; + } + + @Override + public long getIdLong() + { + return id; + } + + @Override + public String toString() + { + return "ForumTag(" + id + ')'; + } + + @Override + public int hashCode() + { + return Long.hashCode(id); + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) + return true; + if (!(obj instanceof ForumTagSnowflakeImpl)) + return false; + return ((ForumTagSnowflakeImpl) obj).id == 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 fe3c0474e3..0142ec049f 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java @@ -107,6 +107,7 @@ public class GuildImpl implements Guild private final SortedSnowflakeCacheViewImpl newsChannelCache = new SortedSnowflakeCacheViewImpl<>(NewsChannel.class, Channel::getName, Comparator.naturalOrder()); private final SortedSnowflakeCacheViewImpl stageChannelCache = new SortedSnowflakeCacheViewImpl<>(StageChannel.class, Channel::getName, Comparator.naturalOrder()); private final SortedSnowflakeCacheViewImpl threadChannelCache = new SortedSnowflakeCacheViewImpl<>(ThreadChannel.class, Channel::getName, Comparator.naturalOrder()); + private final SortedSnowflakeCacheViewImpl forumChannelCache = new SortedSnowflakeCacheViewImpl<>(ForumChannel.class, Channel::getName, Comparator.naturalOrder()); private final SortedSnowflakeCacheViewImpl roleCache = new SortedSnowflakeCacheViewImpl<>(Role.class, Role::getName, Comparator.reverseOrder()); private final SnowflakeCacheViewImpl emojicache = new SnowflakeCacheViewImpl<>(RichCustomEmoji.class, RichCustomEmoji::getName); private final SnowflakeCacheViewImpl stickerCache = new SnowflakeCacheViewImpl<>(GuildSticker.class, GuildSticker::getName); @@ -159,6 +160,7 @@ public void invalidate() SnowflakeCacheViewImpl textView = getJDA().getTextChannelsView(); SnowflakeCacheViewImpl threadView = getJDA().getThreadChannelsView(); SnowflakeCacheViewImpl newsView = getJDA().getNewsChannelView(); + SnowflakeCacheViewImpl forumView = getJDA().getForumChannelsView(); SnowflakeCacheViewImpl voiceView = getJDA().getVoiceChannelsView(); SnowflakeCacheViewImpl categoryView = getJDA().getCategoriesView(); @@ -184,6 +186,11 @@ public void invalidate() getNewsChannelCache() .forEachUnordered(chan -> newsView.getMap().remove(chan.getIdLong())); } + try (UnlockHook hook = forumView.writeLock()) + { + getForumChannelCache() + .forEachUnordered(chan -> forumView.getMap().remove(chan.getIdLong())); + } try (UnlockHook hook = voiceView.writeLock()) { getVoiceChannelCache() @@ -646,6 +653,13 @@ public SortedSnowflakeCacheView getVoiceChannelCache() return voiceChannelCache; } + @Nonnull + @Override + public SortedSnowflakeCacheView getForumChannelCache() + { + return forumChannelCache; + } + @Nonnull @Override public SortedSnowflakeCacheView getStageChannelCache() @@ -694,16 +708,19 @@ public List getChannels(boolean includeHidden) SnowflakeCacheViewImpl stageView = getStageChannelsView(); SnowflakeCacheViewImpl textView = getTextChannelsView(); SnowflakeCacheViewImpl newsView = getNewsChannelView(); + SnowflakeCacheViewImpl forumView = getForumChannelsView(); List textChannels; List newsChannels; List voiceChannels; List stageChannels; + List forumChannels; List categories; try (UnlockHook categoryHook = categoryView.readLock(); UnlockHook voiceHook = voiceView.readLock(); UnlockHook textHook = textView.readLock(); UnlockHook newsHook = newsView.readLock(); - UnlockHook stageHook = stageView.readLock()) + UnlockHook stageHook = stageView.readLock(); + UnlockHook forumHook = forumView.readLock()) { if (includeHidden) { @@ -711,6 +728,7 @@ public List getChannels(boolean includeHidden) newsChannels = newsView.asList(); voiceChannels = voiceView.asList(); stageChannels = stageView.asList(); + forumChannels = forumView.asList(); } else { @@ -718,6 +736,7 @@ public List getChannels(boolean includeHidden) newsChannels = newsView.stream().filter(filterHidden).collect(Collectors.toList()); voiceChannels = voiceView.stream().filter(filterHidden).collect(Collectors.toList()); stageChannels = stageView.stream().filter(filterHidden).collect(Collectors.toList()); + forumChannels = forumView.stream().filter(filterHidden).collect(Collectors.toList()); } categories = categoryView.asList(); // we filter categories out when they are empty (no visible channels inside) channels = new ArrayList<>((int) categoryView.size() + voiceChannels.size() + textChannels.size() + newsChannels.size() + stageChannels.size()); @@ -727,6 +746,7 @@ public List getChannels(boolean includeHidden) newsChannels.stream().filter(it -> it.getParentCategory() == null).forEach(channels::add); voiceChannels.stream().filter(it -> it.getParentCategory() == null).forEach(channels::add); stageChannels.stream().filter(it -> it.getParentCategory() == null).forEach(channels::add); + forumChannels.stream().filter(it -> it.getParentCategory() == null).forEach(channels::add); Collections.sort(channels); for (Category category : categories) @@ -1205,8 +1225,18 @@ public RestAction> retrieveActiveThreads() threadObj.put("member", selfThreadMemberObj); } - ThreadChannel thread = builder.createThreadChannel(threadObj, this.getIdLong()); - list.add(thread); + try + { + ThreadChannel thread = builder.createThreadChannel(threadObj, this.getIdLong()); + list.add(thread); + } + catch (Exception e) + { + if (EntityBuilder.MISSING_CHANNEL.equals(e.getMessage())) + EntityBuilder.LOG.debug("Discarding thread without cached parent channel. JSON: {}", threadObj); + else + EntityBuilder.LOG.warn("Failed to create thread channel. JSON: {}", threadObj, e); + } } return Collections.unmodifiableList(list); @@ -1634,60 +1664,52 @@ public AuditableRestAction transferOwnership(@Nonnull Member newOwner) @Override public ChannelAction createTextChannel(@Nonnull String name, Category parent) { - checkCanCreateChannel(parent); - - Checks.notBlank(name, "Name"); - name = name.trim(); - Checks.notLonger(name, 100, "Name"); - return new ChannelActionImpl<>(TextChannel.class, name, this, ChannelType.TEXT).setParent(parent); + return createChannel(ChannelType.TEXT, TextChannel.class, name, parent); } @Nonnull @Override public ChannelAction createNewsChannel(@Nonnull String name, Category parent) { - checkCanCreateChannel(parent); - - Checks.notBlank(name, "Name"); - name = name.trim(); - Checks.notLonger(name, 100, "Name"); - return new ChannelActionImpl<>(NewsChannel.class, name, this, ChannelType.NEWS).setParent(parent); + return createChannel(ChannelType.NEWS, NewsChannel.class, name, parent); } @Nonnull @Override public ChannelAction createVoiceChannel(@Nonnull String name, Category parent) { - checkCanCreateChannel(parent); - - Checks.notBlank(name, "Name"); - name = name.trim(); - Checks.notLonger(name, 100, "Name"); - return new ChannelActionImpl<>(VoiceChannel.class, name, this, ChannelType.VOICE).setParent(parent); + return createChannel(ChannelType.VOICE, VoiceChannel.class, name, parent); } @Nonnull @Override public ChannelAction createStageChannel(@Nonnull String name, Category parent) { - checkCanCreateChannel(parent); + return createChannel(ChannelType.STAGE, StageChannel.class, name, parent); + } - Checks.notBlank(name, "Name"); - name = name.trim(); - Checks.notLonger(name, 100, "Name"); - return new ChannelActionImpl<>(StageChannel.class, name, this, ChannelType.STAGE).setParent(parent); + @Nonnull + @Override + public ChannelAction createForumChannel(@Nonnull String name, Category parent) + { + return createChannel(ChannelType.FORUM, ForumChannel.class, name, parent); } @Nonnull @Override public ChannelAction createCategory(@Nonnull String name) { - checkPermission(Permission.MANAGE_CHANNEL); + return createChannel(ChannelType.CATEGORY, Category.class, name, null); + } + + private ChannelAction createChannel(ChannelType type, Class clazz, String name, Category parent) + { + checkCanCreateChannel(parent); Checks.notBlank(name, "Name"); name = name.trim(); Checks.notEmpty(name, "Name"); Checks.notLonger(name, 100, "Name"); - return new ChannelActionImpl<>(Category.class, name, this, ChannelType.CATEGORY); + return new ChannelActionImpl<>(clazz, name, this, type).setParent(parent); } @Nonnull @@ -2104,6 +2126,11 @@ public SortedSnowflakeCacheViewImpl getThreadChannelsView() return threadChannelCache; } + public SortedSnowflakeCacheViewImpl getForumChannelsView() + { + return forumChannelCache; + } + public SortedSnowflakeCacheViewImpl getRolesView() { return roleCache; diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/AbstractChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/AbstractChannelImpl.java index c08e70f0d2..c484761160 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/channel/AbstractChannelImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/AbstractChannelImpl.java @@ -111,6 +111,13 @@ public Category asCategory() return Helpers.safeChannelCast(this, Category.class); } + @Nonnull + @Override + public ForumChannel asForumChannel() + { + return Helpers.safeChannelCast(this, ForumChannel.class); + } + @Nonnull public MessageChannel asMessageChannel() { diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/CategoryImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/CategoryImpl.java index c2bfff8db2..abd1bba431 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/CategoryImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/CategoryImpl.java @@ -21,10 +21,7 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.PermissionOverride; import net.dv8tion.jda.api.entities.channel.ChannelType; -import net.dv8tion.jda.api.entities.channel.concrete.Category; -import net.dv8tion.jda.api.entities.channel.concrete.StageChannel; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; -import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.entities.channel.concrete.*; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.managers.channel.concrete.CategoryManager; import net.dv8tion.jda.api.requests.restaction.ChannelAction; @@ -75,6 +72,14 @@ public ChannelAction createTextChannel(@Nonnull String name) return trySync(action); } + @Nonnull + @Override + public ChannelAction createNewsChannel(@Nonnull String name) + { + ChannelAction action = getGuild().createNewsChannel(name, this); + return trySync(action); + } + @Nonnull @Override public ChannelAction createVoiceChannel(@Nonnull String name) @@ -91,6 +96,14 @@ public ChannelAction createStageChannel(@Nonnull String name) return trySync(action); } + @Nonnull + @Override + public ChannelAction createForumChannel(@Nonnull String name) + { + ChannelAction action = getGuild().createForumChannel(name, this); + return trySync(action); + } + @Nonnull @Override public CategoryOrderAction modifyTextChannelPositions() diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java index 7b724676e4..468f937a24 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java @@ -16,13 +16,19 @@ package net.dv8tion.jda.internal.entities.channel.concrete; +import gnu.trove.set.TLongSet; +import gnu.trove.set.hash.TLongHashSet; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.ThreadMember; +import net.dv8tion.jda.api.entities.channel.ChannelFlag; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.attribute.IPermissionContainer; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; import net.dv8tion.jda.api.entities.channel.unions.IThreadContainerUnion; import net.dv8tion.jda.api.managers.channel.concrete.ThreadChannelManager; import net.dv8tion.jda.api.requests.RestAction; @@ -47,8 +53,10 @@ import javax.annotation.Nullable; import java.time.OffsetDateTime; import java.util.Collections; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; +import java.util.stream.LongStream; public class ThreadChannelImpl extends AbstractGuildChannelImpl implements ThreadChannel, @@ -57,18 +65,21 @@ public class ThreadChannelImpl extends AbstractGuildChannelImpl threadMembers = new CacheView.SimpleCacheView<>(ThreadMember.class, null); + private TLongSet appliedTags = new TLongHashSet(ForumChannel.MAX_POST_TAGS); private AutoArchiveDuration autoArchiveDuration; + private IThreadContainerUnion parentChannel; private boolean locked; private boolean archived; private boolean invitable; - private long parentChannelId; private long archiveTimestamp; private long creationTimestamp; private long ownerId; private long latestMessageId; private int messageCount; + private int totalMessageCount; private int memberCount; private int slowmode; + private int flags; public ThreadChannelImpl(long id, GuildImpl guild, ChannelType type) { @@ -76,6 +87,13 @@ public ThreadChannelImpl(long id, GuildImpl guild, ChannelType type) this.type = type; } + @Nonnull + @Override + public EnumSet getFlags() + { + return ChannelFlag.fromRaw(flags); + } + @Nonnull @Override public ChannelType getType() @@ -95,6 +113,12 @@ public int getMessageCount() return messageCount; } + @Override + public int getTotalMessageCount() + { + return totalMessageCount; + } + @Override public int getMemberCount() { @@ -118,15 +142,28 @@ public boolean canTalk(@Nonnull Member member) @Override public List getMembers() { - return null; + return Collections.emptyList(); } @Nonnull @Override - @SuppressWarnings("ConstantConditions") public IThreadContainerUnion getParentChannel() { - return (IThreadContainerUnion) guild.getGuildChannelById(parentChannelId); + return parentChannel; + } + + @Nonnull + @Override + public List getAppliedTags() + { + IThreadContainerUnion parent = getParentChannel(); + if (parent.getType() != ChannelType.FORUM) + return Collections.emptyList(); + return parent.asForumChannel() + .getAvailableTagCache() + .streamUnordered() + .filter(tag -> this.appliedTags.contains(tag.getIdLong())) + .collect(Helpers.toUnmodifiableList()); } @Nonnull @@ -136,6 +173,7 @@ public RestAction retrieveParentMessage() return this.getParentMessageChannel().retrieveMessageById(this.getIdLong()); } + @Nonnull @Override public IPermissionContainer getPermissionContainer() { @@ -315,9 +353,9 @@ public ThreadChannelImpl setAutoArchiveDuration(AutoArchiveDuration autoArchiveD return this; } - public ThreadChannelImpl setParentChannelId(long parentChannelId) + public ThreadChannelImpl setParentChannel(IThreadContainer channel) { - this.parentChannelId = parentChannelId; + this.parentChannel = (IThreadContainerUnion) channel; return this; } @@ -363,6 +401,12 @@ public ThreadChannelImpl setMessageCount(int messageCount) return this; } + public ThreadChannelImpl setTotalMessageCount(int messageCount) + { + this.totalMessageCount = Math.max(messageCount, this.messageCount); // If this is 0 we use the older count + return this; + } + public ThreadChannelImpl setMemberCount(int memberCount) { this.memberCount = memberCount; @@ -375,11 +419,35 @@ public ThreadChannelImpl setSlowmode(int slowmode) return this; } + public ThreadChannelImpl setAppliedTags(LongStream tags) + { + TLongSet set = new TLongHashSet(ForumChannel.MAX_POST_TAGS); + tags.forEach(set::add); + this.appliedTags = set; + return this; + } + + public ThreadChannelImpl setFlags(int flags) + { + this.flags = flags; + return this; + } + public long getArchiveTimestamp() { return archiveTimestamp; } + public TLongSet getAppliedTagsSet() + { + return appliedTags; + } + + + public int getRawFlags() + { + return flags; + } // -- Object overrides -- @@ -391,8 +459,7 @@ public String toString() private void checkUnarchived() { - if (archived) { + if (archived) throw new IllegalStateException("Cannot modify a ThreadChannel while it is archived!"); - } } } diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/middleman/AbstractStandardGuildMessageChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/middleman/AbstractStandardGuildMessageChannelImpl.java index 5b564b4432..925e63cff5 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/channel/middleman/AbstractStandardGuildMessageChannelImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/middleman/AbstractStandardGuildMessageChannelImpl.java @@ -27,6 +27,7 @@ public abstract class AbstractStandardGuildMessageChannelImpl> extends IThreadContainer, IThreadContainerUnion, @@ -38,11 +37,15 @@ public interface IThreadContainerMixin> exten { // ---- Default implementations of interface ---- @Nonnull - @CheckReturnValue @Override - default ThreadChannelAction createThreadChannel(String name, boolean isPrivate) + default ThreadChannelAction createThreadChannel(@Nonnull String name, boolean isPrivate) { - checkPermission(Permission.VIEW_CHANNEL); + Checks.notNull(name, "Name"); + name = name.trim(); + Checks.notEmpty(name, "Name"); + Checks.notLonger(name, 100, "Name"); + + Checks.checkAccess(getGuild().getSelfMember(), this); if (isPrivate) { if (!getGuild().getFeatures().contains("PRIVATE_THREADS")) @@ -64,21 +67,25 @@ default ThreadChannelAction createThreadChannel(String name, boolean isPrivate) } @Nonnull - @CheckReturnValue @Override - default ThreadChannelAction createThreadChannel(String name, long messageId) + default ThreadChannelAction createThreadChannel(@Nonnull String name, long messageId) { - checkPermission(Permission.VIEW_CHANNEL); + Checks.notNull(name, "Name"); + name = name.trim(); + Checks.notEmpty(name, "Name"); + Checks.notLonger(name, 100, "Name"); + + Checks.checkAccess(getGuild().getSelfMember(), this); checkPermission(Permission.CREATE_PUBLIC_THREADS); return new ThreadChannelActionImpl(this, name, Long.toUnsignedString(messageId)); } @Nonnull - @CheckReturnValue @Override default ThreadChannelPaginationAction retrieveArchivedPublicThreadChannels() { + Checks.checkAccess(getGuild().getSelfMember(), this); checkPermission(Permission.MESSAGE_HISTORY); Route.CompiledRoute route = Route.Channels.LIST_PUBLIC_ARCHIVED_THREADS.compile(getId()); @@ -86,10 +93,10 @@ default ThreadChannelPaginationAction retrieveArchivedPublicThreadChannels() } @Nonnull - @CheckReturnValue @Override default ThreadChannelPaginationAction retrieveArchivedPrivateThreadChannels() { + Checks.checkAccess(getGuild().getSelfMember(), this); checkPermission(Permission.MESSAGE_HISTORY); checkPermission(Permission.MANAGE_THREADS); @@ -98,13 +105,15 @@ default ThreadChannelPaginationAction retrieveArchivedPrivateThreadChannels() } @Nonnull - @CheckReturnValue @Override default ThreadChannelPaginationAction retrieveArchivedPrivateJoinedThreadChannels() { + Checks.checkAccess(getGuild().getSelfMember(), this); checkPermission(Permission.MESSAGE_HISTORY); Route.CompiledRoute route = Route.Channels.LIST_JOINED_PRIVATE_ARCHIVED_THREADS.compile(getId()); return new ThreadChannelPaginationActionImpl(getJDA(), route, this, true); } + + T setDefaultThreadSlowmode(int slowmode); } diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ChannelCreateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ChannelCreateHandler.java index 2ec4fc4f14..5454dad81c 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/ChannelCreateHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/ChannelCreateHandler.java @@ -46,7 +46,8 @@ protected Long handleInternally(DataObject content) } Channel channel = buildChannel(type, content, guildId); - if (channel == null) { + if (channel == null) + { WebSocketClient.LOG.debug("Discord provided an CREATE_CHANNEL event with an unknown channel type! JSON: {}", content); return null; } @@ -66,6 +67,7 @@ private Channel buildChannel(ChannelType type, DataObject content, long guildId) case VOICE: return builder.createVoiceChannel(content, guildId); case STAGE: return builder.createStageChannel(content, guildId); case CATEGORY: return builder.createCategory(content, guildId); + case FORUM: return builder.createForumChannel(content, guildId); default: return null; diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ChannelDeleteHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ChannelDeleteHandler.java index e50187e78f..c0a36c6b5e 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/ChannelDeleteHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/ChannelDeleteHandler.java @@ -120,6 +120,7 @@ protected Long handleInternally(DataObject content) new ChannelDeleteEvent( getJDA(), responseNumber, channel)); + break; } case CATEGORY: @@ -138,6 +139,22 @@ protected Long handleInternally(DataObject content) category)); break; } + case FORUM: + { + ForumChannel channel = getJDA().getForumChannelsView().remove(channelId); + if (channel == null || guild == null) + { + WebSocketClient.LOG.debug("CHANNEL_DELETE attempted to delete a forum channel that is not yet cached. JSON: {}", content); + return null; + } + + guild.getForumChannelsView().remove(channel.getIdLong()); + getJDA().handleEvent( + new ChannelDeleteEvent( + getJDA(), responseNumber, + channel)); + break; + } case PRIVATE: { SnowflakeCacheViewImpl privateView = getJDA().getPrivateChannelsView(); diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java index 3a82b80a2c..ab9ce6520e 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java @@ -18,18 +18,25 @@ import gnu.trove.map.TLongObjectMap; import gnu.trove.map.hash.TLongObjectHashMap; +import gnu.trove.set.TLongSet; +import gnu.trove.set.hash.TLongHashSet; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Region; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.IPermissionHolder; import net.dv8tion.jda.api.entities.PermissionOverride; +import net.dv8tion.jda.api.entities.channel.ChannelFlag; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; -import net.dv8tion.jda.api.entities.channel.concrete.Category; -import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; -import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.concrete.*; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.entities.emoji.EmojiUnion; +import net.dv8tion.jda.api.events.channel.forum.ForumTagAddEvent; +import net.dv8tion.jda.api.events.channel.forum.ForumTagRemoveEvent; +import net.dv8tion.jda.api.events.channel.forum.update.ForumTagUpdateEmojiEvent; +import net.dv8tion.jda.api.events.channel.forum.update.ForumTagUpdateModeratedEvent; +import net.dv8tion.jda.api.events.channel.forum.update.ForumTagUpdateNameEvent; import net.dv8tion.jda.api.events.channel.update.*; import net.dv8tion.jda.api.events.guild.override.PermissionOverrideCreateEvent; import net.dv8tion.jda.api.events.guild.override.PermissionOverrideDeleteEvent; @@ -39,14 +46,13 @@ import net.dv8tion.jda.api.utils.data.DataArray; import net.dv8tion.jda.api.utils.data.DataObject; import net.dv8tion.jda.internal.JDAImpl; -import net.dv8tion.jda.internal.entities.EntityBuilder; -import net.dv8tion.jda.internal.entities.GuildImpl; -import net.dv8tion.jda.internal.entities.PermissionOverrideImpl; +import net.dv8tion.jda.internal.entities.*; import net.dv8tion.jda.internal.entities.channel.concrete.*; import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IPermissionContainerMixin; import net.dv8tion.jda.internal.requests.WebSocketClient; import net.dv8tion.jda.internal.utils.UnlockHook; import net.dv8tion.jda.internal.utils.cache.SnowflakeCacheViewImpl; +import net.dv8tion.jda.internal.utils.cache.SortedSnowflakeCacheViewImpl; import java.util.ArrayList; import java.util.List; @@ -78,8 +84,10 @@ protected Long handleInternally(DataObject content) final long channelId = content.getLong("id"); final long parentId = content.isNull("parent_id") ? 0 : content.getLong("parent_id"); final int position = content.getInt("position"); + final int flags = content.getInt("flags", 0); final String name = content.getString("name"); final boolean nsfw = content.getBoolean("nsfw"); + final int defaultThreadSlowmode = content.getInt("default_thread_rate_limit_per_user", 0); final int slowmode = content.getInt("rate_limit_per_user", 0); final DataArray permOverwrites = content.getArray("permission_overwrites"); @@ -110,6 +118,7 @@ protected Long handleInternally(DataObject content) final int oldPosition = textChannel.getPositionRaw(); final boolean oldNsfw = textChannel.isNSFW(); final int oldSlowmode = textChannel.getSlowmode(); + final int oldDefaultThreadSlowmode = textChannel.getDefaultThreadSlowmode(); if (!Objects.equals(oldName, name)) { textChannel.setName(name); @@ -143,7 +152,6 @@ protected Long handleInternally(DataObject content) getJDA(), responseNumber, textChannel, oldPosition, position)); } - if (oldNsfw != nsfw) { textChannel.setNSFW(nsfw); @@ -152,7 +160,6 @@ protected Long handleInternally(DataObject content) getJDA(), responseNumber, textChannel, oldNsfw, nsfw)); } - if (oldSlowmode != slowmode) { textChannel.setSlowmode(slowmode); @@ -161,6 +168,124 @@ protected Long handleInternally(DataObject content) getJDA(), responseNumber, textChannel, oldSlowmode, slowmode)); } + if (oldDefaultThreadSlowmode != defaultThreadSlowmode) + { + textChannel.setDefaultThreadSlowmode(defaultThreadSlowmode); + getJDA().handleEvent( + new ChannelUpdateDefaultThreadSlowmodeEvent( + getJDA(), responseNumber, + textChannel, oldDefaultThreadSlowmode, defaultThreadSlowmode)); + } + break; + } + case FORUM: + { + final String topic = content.getString("topic", null); + final EmojiUnion defaultReaction = content.optObject("default_reaction_emoji") + .map(json -> { + json.opt("emoji_id").ifPresent(id -> json.put("id", id)); + json.opt("emoji_name").ifPresent(n -> json.put("name", n)); + return EntityBuilder.createEmoji(json); + }) + .orElse(null); + + ForumChannelImpl forumChannel = (ForumChannelImpl) channel; + content.optArray("available_tags").ifPresent(array -> handleTagsUpdate(forumChannel, array)); + int sortOrder = content.getInt("default_sort_order", ((ForumChannelImpl) channel).getRawSortOrder()); + + //If any properties changed, update the values and fire the proper events. + final long oldParentId = forumChannel.getParentCategoryIdLong(); + final String oldName = forumChannel.getName(); + final String oldTopic = forumChannel.getTopic(); + final int oldPosition = forumChannel.getPositionRaw(); + final boolean oldNsfw = forumChannel.isNSFW(); + final int oldSlowmode = forumChannel.getSlowmode(); + final int oldDefaultThreadSlowmode = forumChannel.getDefaultThreadSlowmode(); + final int oldFlags = forumChannel.getRawFlags(); + final int oldSortOrder = forumChannel.getRawSortOrder(); + final EmojiUnion oldDefaultReaction = forumChannel.getDefaultReaction(); + + if (!Objects.equals(oldName, name)) + { + forumChannel.setName(name); + getJDA().handleEvent( + new ChannelUpdateNameEvent( + getJDA(), responseNumber, + forumChannel, oldName, name)); + } + if (oldParentId != parentId) + { + final Category oldParent = forumChannel.getParentCategory(); + forumChannel.setParentCategory(parentId); + getJDA().handleEvent( + new ChannelUpdateParentEvent( + getJDA(), responseNumber, + forumChannel, oldParent, forumChannel.getParentCategory())); + } + if (!Objects.equals(oldTopic, topic)) + { + forumChannel.setTopic(topic); + getJDA().handleEvent( + new ChannelUpdateTopicEvent( + getJDA(), responseNumber, + forumChannel, oldTopic, topic)); + } + if (oldPosition != position) + { + forumChannel.setPosition(position); + getJDA().handleEvent( + new ChannelUpdatePositionEvent( + getJDA(), responseNumber, + forumChannel, oldPosition, position)); + } + if (oldNsfw != nsfw) + { + forumChannel.setNSFW(nsfw); + getJDA().handleEvent( + new ChannelUpdateNSFWEvent( + getJDA(), responseNumber, + forumChannel, oldNsfw, nsfw)); + } + if (oldSlowmode != slowmode) + { + forumChannel.setSlowmode(slowmode); + getJDA().handleEvent( + new ChannelUpdateSlowmodeEvent( + getJDA(), responseNumber, + forumChannel, oldSlowmode, slowmode)); + } + if (oldDefaultThreadSlowmode != defaultThreadSlowmode) + { + forumChannel.setDefaultThreadSlowmode(defaultThreadSlowmode); + getJDA().handleEvent( + new ChannelUpdateDefaultThreadSlowmodeEvent( + getJDA(), responseNumber, + forumChannel, oldDefaultThreadSlowmode, defaultThreadSlowmode)); + } + if (oldFlags != flags) + { + forumChannel.setFlags(flags); + getJDA().handleEvent( + new ChannelUpdateFlagsEvent( + getJDA(), responseNumber, + forumChannel, ChannelFlag.fromRaw(oldFlags), ChannelFlag.fromRaw(flags))); + } + if (!Objects.equals(oldDefaultReaction, defaultReaction)) + { + forumChannel.setDefaultReaction(content.optObject("default_reaction_emoji").orElse(null)); + getJDA().handleEvent( + new ChannelUpdateDefaultReactionEvent( + getJDA(), responseNumber, + forumChannel, oldDefaultReaction, defaultReaction)); + } + if (oldSortOrder != sortOrder) + { + forumChannel.setDefaultSortOrder(sortOrder); + getJDA().handleEvent( + new ChannelUpdateDefaultSortOrderEvent( + getJDA(), responseNumber, + forumChannel, ForumChannel.SortOrder.fromKey(oldSortOrder), ForumChannel.SortOrder.fromKey(sortOrder))); + } break; } case NEWS: @@ -573,4 +698,68 @@ private void handleHideChildThreads(IThreadContainer channel) api.handleEvent(new ThreadHiddenEvent(api, responseNumber, thread)); } } + + private void handleTagsUpdate(ForumChannelImpl channel, DataArray tags) + { + if (!api.isCacheFlagSet(CacheFlag.FORUM_TAGS)) + return; + EntityBuilder builder = api.getEntityBuilder(); + + SortedSnowflakeCacheViewImpl view = channel.getAvailableTagCache(); + + try (UnlockHook hook = view.writeLock()) + { + TLongObjectMap cache = view.getMap(); + TLongSet removedTags = new TLongHashSet(cache.keySet()); + + for (int i = 0; i < tags.length(); i++) + { + DataObject tagJson = tags.getObject(i); + long id = tagJson.getUnsignedLong("id"); + if (removedTags.remove(id)) + { + ForumTagImpl impl = (ForumTagImpl) cache.get(id); + if (impl == null) + continue; + + String name = tagJson.getString("name"); + boolean moderated = tagJson.getBoolean("moderated"); + + String oldName = impl.getName(); + EmojiUnion oldEmoji = impl.getEmoji(); + + impl.setEmoji(tagJson); + + impl.setPosition(i); + if (!Objects.equals(oldEmoji, impl.getEmoji())) + { + api.handleEvent(new ForumTagUpdateEmojiEvent(api, responseNumber, channel, impl, oldEmoji)); + } + if (!name.equals(oldName)) + { + impl.setName(name); + api.handleEvent(new ForumTagUpdateNameEvent(api, responseNumber, channel, impl, oldName)); + } + if (moderated != impl.isModerated()) + { + impl.setModerated(moderated); + api.handleEvent(new ForumTagUpdateModeratedEvent(api, responseNumber, channel, impl, moderated)); + } + } + else + { + ForumTag tag = builder.createForumTag(channel, tagJson, i); + cache.put(id, tag); + api.handleEvent(new ForumTagAddEvent(api, responseNumber, channel, tag)); + } + } + + removedTags.forEach(id -> { + ForumTag tag = cache.remove(id); + if (tag != null) + api.handleEvent(new ForumTagRemoveEvent(api, responseNumber, channel, tag)); + return true; + }); + } + } } diff --git a/src/main/java/net/dv8tion/jda/internal/handle/MessageCreateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/MessageCreateHandler.java index 7d89a40e4e..8fdef7408e 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/MessageCreateHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/MessageCreateHandler.java @@ -124,10 +124,8 @@ protected Long handleInternally(DataObject content) { ThreadChannelImpl gThread = (ThreadChannelImpl) channel; - //Discord will only ever allow this property to show up to 50, - // so we don't want to update it to be over 50 because we don't want users to use it incorrectly. - int newMessageCount = Math.min(gThread.getMessageCount() + 1, 50); - gThread.setMessageCount(newMessageCount); + gThread.setMessageCount(gThread.getMessageCount() + 1); + gThread.setTotalMessageCount(gThread.getTotalMessageCount() + 1); } } else diff --git a/src/main/java/net/dv8tion/jda/internal/handle/MessageDeleteHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/MessageDeleteHandler.java index d5dabfa224..663e120366 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/MessageDeleteHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/MessageDeleteHandler.java @@ -77,13 +77,8 @@ protected Long handleInternally(DataObject content) { ThreadChannelImpl gThread = (ThreadChannelImpl) channel; - //If we have less than 50 messages then we can still accurately track how many messages are in the message count. - //Once we exceed 50 messages Discord caps this value, so we cannot confidently decrement it. - int messageCount = gThread.getMessageCount(); - if (messageCount < 50 && messageCount > 0) - { - gThread.setMessageCount(messageCount - 1); - } + gThread.setMessageCount(Math.max(0, gThread.getMessageCount() - 1)); + // Not decrementing totalMessageCount since that should include deleted as well } getJDA().handleEvent(new MessageDeleteEvent(getJDA(), responseNumber, messageId, channel)); diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ThreadCreateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ThreadCreateHandler.java index 310215c616..f7a0efdd80 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/ThreadCreateHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/ThreadCreateHandler.java @@ -20,6 +20,7 @@ import net.dv8tion.jda.api.events.channel.ChannelCreateEvent; import net.dv8tion.jda.api.utils.data.DataObject; import net.dv8tion.jda.internal.JDAImpl; +import net.dv8tion.jda.internal.entities.EntityBuilder; public class ThreadCreateHandler extends SocketHandler { @@ -35,9 +36,20 @@ protected Long handleInternally(DataObject content) if (api.getGuildSetupController().isLocked(guildId)) return guildId; - ThreadChannel thread = api.getEntityBuilder().createThreadChannel(content, guildId); + try + { + ThreadChannel thread = api.getEntityBuilder().createThreadChannel(content, guildId); + api.handleEvent(new ChannelCreateEvent(api, responseNumber, thread)); + } + catch (IllegalArgumentException ex) + { + if (!EntityBuilder.MISSING_CHANNEL.equals(ex.getMessage())) + throw ex; - api.handleEvent(new ChannelCreateEvent(api, responseNumber, thread)); + long parentId = content.getUnsignedLong("parent_id"); + EventCache.LOG.debug("Caching THREAD_CREATE_EVENT for channel with uncached parent. Parent ID: {}", parentId); + api.getEventCache().cache(EventCache.Type.CHANNEL, parentId, responseNumber, allContent, this::handle); + } return null; } diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ThreadListSyncHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ThreadListSyncHandler.java index 20aabfbc7b..9f92556218 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/ThreadListSyncHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/ThreadListSyncHandler.java @@ -42,9 +42,17 @@ protected Long handleInternally(DataObject content) for (int i = 0; i < threadsArrayJson.length(); i++) { DataObject threadJson = threadsArrayJson.getObject(i); - ThreadChannel thread = entityBuilder.createThreadChannel(threadJson, guildId); - - api.handleEvent(new ThreadRevealedEvent(api, responseNumber, thread)); + try + { + ThreadChannel thread = entityBuilder.createThreadChannel(threadJson, guildId); + api.handleEvent(new ThreadRevealedEvent(api, responseNumber, thread)); + } + catch (IllegalArgumentException ex) + { + if (!EntityBuilder.MISSING_CHANNEL.equals(ex.getMessage())) + throw ex; + EntityBuilder.LOG.debug("Discarding thread on sync because of missing parent channel cache. JSON: {}", threadJson); + } } return null; diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ThreadUpdateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ThreadUpdateHandler.java index 25ffffd2fe..da024291c8 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/ThreadUpdateHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/ThreadUpdateHandler.java @@ -16,14 +16,20 @@ package net.dv8tion.jda.internal.handle; +import gnu.trove.set.TLongSet; +import net.dv8tion.jda.api.entities.channel.ChannelFlag; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.events.channel.update.*; +import net.dv8tion.jda.api.utils.cache.CacheFlag; +import net.dv8tion.jda.api.utils.data.DataArray; import net.dv8tion.jda.api.utils.data.DataObject; import net.dv8tion.jda.internal.JDAImpl; import net.dv8tion.jda.internal.entities.channel.concrete.ThreadChannelImpl; import net.dv8tion.jda.internal.utils.Helpers; +import java.util.List; import java.util.Objects; +import java.util.stream.LongStream; public class ThreadUpdateHandler extends SocketHandler { @@ -62,6 +68,7 @@ protected Long handleInternally(DataObject content) final DataObject threadMetadata = content.getObject("thread_metadata"); final String name = content.getString("name"); + final int flags = content.getInt("flags", 0); final ThreadChannel.AutoArchiveDuration autoArchiveDuration = ThreadChannel.AutoArchiveDuration.fromKey(threadMetadata.getInt("auto_archive_duration")); final boolean locked = threadMetadata.getBoolean("locked"); final boolean archived = threadMetadata.getBoolean("archived"); @@ -76,6 +83,8 @@ protected Long handleInternally(DataObject content) final boolean oldInvitable = !thread.isPublic() && thread.isInvitable(); final long oldArchiveTimestamp = thread.getArchiveTimestamp(); final int oldSlowmode = thread.getSlowmode(); + final int oldFlags = thread.getRawFlags(); + //TODO should these be Thread specific events? if (!Objects.equals(oldName, name)) @@ -86,6 +95,14 @@ protected Long handleInternally(DataObject content) getJDA(), responseNumber, thread, oldName, name)); } + if (oldFlags != flags) + { + thread.setFlags(flags); + api.handleEvent( + new ChannelUpdateFlagsEvent( + getJDA(), responseNumber, + thread, ChannelFlag.fromRaw(oldFlags), ChannelFlag.fromRaw(flags))); + } if (oldSlowmode != slowmode) { thread.setSlowmode(slowmode); @@ -135,6 +152,25 @@ protected Long handleInternally(DataObject content) thread, oldInvitable, invitable)); } + if (api.isCacheFlagSet(CacheFlag.FORUM_TAGS) && !content.isNull("applied_tags")) + { + final TLongSet oldTags = thread.getAppliedTagsSet(); + thread.setAppliedTags(content.getArray("applied_tags") + .stream(DataArray::getUnsignedLong) + .mapToLong(Long::longValue)); + final TLongSet tags = thread.getAppliedTagsSet(); + + if (!oldTags.equals(tags)) + { + List oldTagList = LongStream.of(oldTags.toArray()).boxed().collect(Helpers.toUnmodifiableList()); + List newTagList = LongStream.of(tags.toArray()).boxed().collect(Helpers.toUnmodifiableList()); + api.handleEvent( + new ChannelUpdateAppliedTagsEvent( + api, responseNumber, + thread, oldTagList, newTagList)); + } + } + return null; } } diff --git a/src/main/java/net/dv8tion/jda/internal/managers/channel/ChannelManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/channel/ChannelManagerImpl.java index f9ace1b03f..6924f5abb2 100644 --- a/src/main/java/net/dv8tion/jda/internal/managers/channel/ChannelManagerImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/managers/channel/ChannelManagerImpl.java @@ -21,19 +21,26 @@ import gnu.trove.set.hash.TLongHashSet; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Region; -import net.dv8tion.jda.api.entities.IPermissionHolder; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.PermissionOverride; -import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.ChannelFlag; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.attribute.IPermissionContainer; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; import net.dv8tion.jda.api.entities.channel.concrete.Category; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; -import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.entities.channel.forums.BaseForumTag; +import net.dv8tion.jda.api.entities.channel.forums.ForumTagSnowflake; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel; +import net.dv8tion.jda.api.entities.channel.unions.IThreadContainerUnion; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji; import net.dv8tion.jda.api.exceptions.InsufficientPermissionException; import net.dv8tion.jda.api.managers.channel.ChannelManager; +import net.dv8tion.jda.api.utils.data.DataArray; import net.dv8tion.jda.api.utils.data.DataObject; import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IPermissionContainerMixin; import net.dv8tion.jda.internal.entities.channel.mixin.middleman.GuildChannelMixin; @@ -46,15 +53,27 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; +import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; +import java.util.List; +import java.util.stream.Collectors; @SuppressWarnings("unchecked") //We do a lot of (M) and (T) casting that we know is correct but the compiler warns about. public class ChannelManagerImpl> extends ManagerBase implements ChannelManager { + private static final EnumSet SLOWMODE_SUPPORTED = EnumSet.of(ChannelType.TEXT, ChannelType.FORUM, + ChannelType.GUILD_PUBLIC_THREAD, ChannelType.GUILD_NEWS_THREAD, ChannelType.GUILD_PRIVATE_THREAD); + private static final EnumSet NSFW_SUPPORTED = EnumSet.of(ChannelType.TEXT, ChannelType.VOICE, ChannelType.FORUM, ChannelType.NEWS); + private static final EnumSet TOPIC_SUPPORTED = EnumSet.of(ChannelType.TEXT, ChannelType.FORUM, ChannelType.NEWS); + protected T channel; + protected final EnumSet flags; protected ThreadChannel.AutoArchiveDuration autoArchiveDuration; + protected List availableTags; + protected List appliedTags; + protected Emoji defaultReactionEmoji; protected ChannelType type; protected String name; protected String parent; @@ -73,18 +92,12 @@ public class ChannelManagerImpl overridesAdd; protected final TLongSet overridesRem; - /** - * Creates a new ChannelManager instance - * - * @param channel - * {@link GuildChannel GuildChannel} that should be modified - *
    Either {@link VoiceChannel Voice}- or {@link TextChannel TextChannel} - */ public ChannelManagerImpl(T channel) { super(channel.getJDA(), Route.Channels.MODIFY_CHANNEL.compile(channel.getId())); this.channel = channel; this.type = channel.getType(); + this.flags = channel.getFlags(); if (isPermissionChecksEnabled()) checkPermissions(); @@ -118,6 +131,12 @@ public M reset(long fields) this.topic = null; if ((fields & REGION) == REGION) this.region = null; + if ((fields & AVAILABLE_TAGS) == AVAILABLE_TAGS) + this.availableTags = null; + if ((fields & APPLIED_TAGS) == APPLIED_TAGS) + this.appliedTags = null; + if ((fields & DEFAULT_REACTION) == DEFAULT_REACTION) + this.defaultReactionEmoji = null; if ((fields & PERMISSION) == PERMISSION) { withLock(lock, (lock) -> @@ -126,6 +145,23 @@ public M reset(long fields) this.overridesAdd.clear(); }); } + + if ((fields & PINNED) == PINNED) + { + if (channel.getFlags().contains(ChannelFlag.PINNED)) + this.flags.add(ChannelFlag.PINNED); + else + this.flags.remove(ChannelFlag.PINNED); + } + + if ((fields & REQUIRE_TAG) == REQUIRE_TAG) + { + if (channel.getFlags().contains(ChannelFlag.REQUIRE_TAG)) + this.flags.add(ChannelFlag.REQUIRE_TAG); + else + this.flags.remove(ChannelFlag.REQUIRE_TAG); + } + return (M) this; } @@ -149,6 +185,11 @@ public M reset() this.parent = null; this.topic = null; this.region = null; + this.availableTags = null; + this.appliedTags = null; + this.defaultReactionEmoji = null; + this.flags.clear(); + this.flags.addAll(channel.getFlags()); withLock(lock, (lock) -> { this.overridesRem.clear(); @@ -188,9 +229,7 @@ public M clearOverridesRemoved() public M putPermissionOverride(@Nonnull IPermissionHolder permHolder, long allow, long deny) { if (!(channel instanceof IPermissionContainer)) - { throw new IllegalStateException("Can only set permissions on Channels that implement IPermissionContainer"); - } Checks.notNull(permHolder, "PermissionHolder"); Checks.check(permHolder.getGuild().equals(getGuild()), "PermissionHolder is not from the same Guild!"); @@ -341,7 +380,7 @@ public M setName(@Nonnull String name) Checks.notBlank(name, "Name"); name = name.trim(); Checks.notEmpty(name, "Name"); - Checks.notLonger(name, 100, "Name"); + Checks.notLonger(name, Channel.MAX_NAME_LENGTH, "Name"); this.name = name; set |= NAME; return (M) this; @@ -413,10 +452,14 @@ public M setPosition(int position) @CheckReturnValue public M setTopic(String topic) { - if (type != ChannelType.TEXT && type != ChannelType.NEWS) - throw new IllegalStateException("Can only set topic on text and news channels"); + Checks.checkSupportedChannelTypes(TOPIC_SUPPORTED, type, "topic"); if (topic != null) - Checks.notLonger(topic, 1024, "Topic"); + { + if (type == ChannelType.FORUM) + Checks.notLonger(topic, ForumChannel.MAX_FORUM_TOPIC_LENGTH, "Topic"); + else + Checks.notLonger(topic, StandardGuildMessageChannel.MAX_TOPIC_LENGTH, "Topic"); + } this.topic = topic; set |= TOPIC; return (M) this; @@ -426,8 +469,7 @@ public M setTopic(String topic) @CheckReturnValue public M setNSFW(boolean nsfw) { - if (type != ChannelType.TEXT && type != ChannelType.NEWS) - throw new IllegalStateException("Can only set nsfw on text and news channels"); + Checks.checkSupportedChannelTypes(NSFW_SUPPORTED, type, "NSFW (age-restriction)"); this.nsfw = nsfw; set |= NSFW; return (M) this; @@ -437,9 +479,8 @@ public M setNSFW(boolean nsfw) @CheckReturnValue public M setSlowmode(int slowmode) { - if (type != ChannelType.TEXT && !type.isThread()) - throw new IllegalStateException("Can only set slowmode on text channels and threads"); - Checks.check(slowmode <= TextChannel.MAX_SLOWMODE && slowmode >= 0, "Slowmode per user must be between 0 and %d (seconds)!", TextChannel.MAX_SLOWMODE); + Checks.checkSupportedChannelTypes(SLOWMODE_SUPPORTED, type, "slowmode"); + Checks.check(slowmode <= ISlowmodeChannel.MAX_SLOWMODE && slowmode >= 0, "Slowmode per user must be between 0 and %d (seconds)!", ISlowmodeChannel.MAX_SLOWMODE); this.slowmode = slowmode; set |= SLOWMODE; return (M) this; @@ -533,10 +574,66 @@ public M setInvitable(boolean invitable) return (M) this; } + public M setPinned(boolean pinned) + { + if (pinned) + flags.add(ChannelFlag.PINNED); + else + flags.remove(ChannelFlag.PINNED); + set |= PINNED; + return (M) this; + } + + public M setTagRequired(boolean requireTag) + { + if (requireTag) + flags.add(ChannelFlag.REQUIRE_TAG); + else + flags.remove(ChannelFlag.REQUIRE_TAG); + set |= REQUIRE_TAG; + return (M) this; + } + + public M setAvailableTags(List tags) + { + if (type != ChannelType.FORUM) + throw new IllegalStateException("Can only set available tags on forum channels."); + Checks.noneNull(tags, "Available Tags"); + this.availableTags = new ArrayList<>(tags); + set |= AVAILABLE_TAGS; + return (M) this; + } + + public M setAppliedTags(Collection tags) + { + if (type != ChannelType.GUILD_PUBLIC_THREAD) + throw new IllegalStateException("Can only set applied tags on forum post thread channels."); + Checks.noneNull(tags, "Applied Tags"); + Checks.check(tags.size() <= ForumChannel.MAX_POST_TAGS, "Cannot apply more than %d tags to a post thread!", ForumChannel.MAX_POST_TAGS); + ThreadChannel thread = (ThreadChannel) getChannel(); + IThreadContainerUnion parentChannel = thread.getParentChannel(); + if (!(parentChannel instanceof ForumChannel)) + throw new IllegalStateException("Cannot apply tags to threads outside of forum channels."); + if (tags.isEmpty() && parentChannel.asForumChannel().isTagRequired()) + throw new IllegalArgumentException("Cannot remove all tags from a forum post which requires at least one tag! See ForumChannel#isRequireTag()"); + this.appliedTags = tags.stream().map(ISnowflake::getId).collect(Collectors.toList()); + set |= APPLIED_TAGS; + return (M) this; + } + + public M setDefaultReaction(Emoji emoji) + { + if (type != ChannelType.FORUM) + throw new IllegalStateException("Can only set default reaction on forum channels."); + this.defaultReactionEmoji = emoji; + set |= DEFAULT_REACTION; + return (M) this; + } + @Override protected RequestBody finalizeData() { - DataObject frame = DataObject.empty().put("name", getChannel().getName()); + DataObject frame = DataObject.empty(); if (shouldUpdate(NAME)) frame.put("name", name); if (shouldUpdate(TYPE)) @@ -565,6 +662,21 @@ protected RequestBody finalizeData() frame.put("locked", locked); if (shouldUpdate(INVITEABLE)) frame.put("invitable", invitable); + if (shouldUpdate(AVAILABLE_TAGS)) + frame.put("available_tags", DataArray.fromCollection(availableTags)); + if (shouldUpdate(APPLIED_TAGS)) + frame.put("applied_tags", DataArray.fromCollection(appliedTags)); + if (shouldUpdate(PINNED | REQUIRE_TAG)) + frame.put("flags", ChannelFlag.getRaw(flags)); + if (shouldUpdate(DEFAULT_REACTION)) + { + if (defaultReactionEmoji instanceof CustomEmoji) + frame.put("default_reaction_emoji", DataObject.empty().put("emoji_id", ((CustomEmoji) defaultReactionEmoji).getId())); + else if (defaultReactionEmoji instanceof UnicodeEmoji) + frame.put("default_reaction_emoji", DataObject.empty().put("emoji_name", defaultReactionEmoji.getName())); + else + frame.put("default_reaction_emoji", null); + } withLock(lock, (lock) -> { diff --git a/src/main/java/net/dv8tion/jda/internal/managers/channel/concrete/ForumChannelManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/channel/concrete/ForumChannelManagerImpl.java new file mode 100644 index 0000000000..64f55cc48d --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/managers/channel/concrete/ForumChannelManagerImpl.java @@ -0,0 +1,29 @@ +/* + * 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.internal.managers.channel.concrete; + +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.managers.channel.concrete.ForumChannelManager; +import net.dv8tion.jda.internal.managers.channel.ChannelManagerImpl; + +public class ForumChannelManagerImpl extends ChannelManagerImpl implements ForumChannelManager +{ + public ForumChannelManagerImpl(ForumChannel channel) + { + super(channel); + } +} diff --git a/src/main/java/net/dv8tion/jda/internal/requests/Route.java b/src/main/java/net/dv8tion/jda/internal/requests/Route.java index 41a70939c7..f3e4013e72 100644 --- a/src/main/java/net/dv8tion/jda/internal/requests/Route.java +++ b/src/main/java/net/dv8tion/jda/internal/requests/Route.java @@ -232,8 +232,8 @@ public static class Channels public static final Route GET_PERM_OVERRIDE = new Route(GET, "channels/{channel_id}/permissions/{permoverride_id}"); public static final Route FOLLOW_CHANNEL = new Route(POST, "channels/{channel_id}/followers"); - public static final Route CREATE_THREAD_WITH_MESSAGE = new Route(POST, "channels/{channel_id}/messages/{message_id}/threads"); - public static final Route CREATE_THREAD_WITHOUT_MESSAGE = new Route(POST, "channels/{channel_id}/threads"); + public static final Route CREATE_THREAD_FROM_MESSAGE = new Route(POST, "channels/{channel_id}/messages/{message_id}/threads"); + public static final Route CREATE_THREAD = new Route(POST, "channels/{channel_id}/threads"); public static final Route JOIN_THREAD = new Route(PUT, "channels/{channel_id}/thread-members/@me"); public static final Route ADD_THREAD_MEMBER = new Route(PUT, "channels/{channel_id}/thread-members/{user_id}"); public static final Route LEAVE_THREAD = new Route(DELETE, "channels/{channel_id}/thread-members/@me"); diff --git a/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java b/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java index afb1ad2bc3..e59fac7c00 100644 --- a/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java +++ b/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java @@ -756,6 +756,7 @@ protected void invalidate() api.getPrivateChannelsView().clear(); api.getStageChannelView().clear(); api.getThreadChannelsView().clear(); + api.getForumChannelsView().clear(); api.getGuildsView().clear(); api.getUsersView().clear(); diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java index f5559f2329..0154b9c6d8 100644 --- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java @@ -22,10 +22,17 @@ import net.dv8tion.jda.api.Region; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel; import net.dv8tion.jda.api.entities.channel.concrete.Category; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.forums.BaseForumTag; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji; import net.dv8tion.jda.api.exceptions.InsufficientPermissionException; import net.dv8tion.jda.api.requests.Request; import net.dv8tion.jda.api.requests.Response; @@ -41,12 +48,19 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; public class ChannelActionImpl extends AuditableRestActionImpl implements ChannelAction { + private static final EnumSet SLOWMODE_SUPPORTED = EnumSet.of(ChannelType.TEXT, ChannelType.FORUM, + ChannelType.GUILD_PUBLIC_THREAD, ChannelType.GUILD_NEWS_THREAD, ChannelType.GUILD_PRIVATE_THREAD); + private static final EnumSet NSFW_SUPPORTED = EnumSet.of(ChannelType.TEXT, ChannelType.VOICE, ChannelType.FORUM, ChannelType.NEWS); + private static final EnumSet TOPIC_SUPPORTED = EnumSet.of(ChannelType.TEXT, ChannelType.FORUM, ChannelType.NEWS); + protected final TLongObjectMap overrides = new TLongObjectHashMap<>(); protected final Guild guild; protected final Class clazz; @@ -57,17 +71,21 @@ public class ChannelActionImpl extends AuditableRestActi protected Category parent; protected Integer position; - // --text only-- + // --forum only-- + protected List availableTags; + protected Emoji defaultReactionEmoji; + + // --text/forum/voice only-- protected Integer slowmode = null; - // --text and news-- + // --text/forum/voice/news-- protected String topic = null; protected Boolean nsfw = null; // --voice only-- protected Integer userlimit = null; - // --voice and stage-- + // --audio only-- protected Integer bitrate = null; protected Region region = null; @@ -80,6 +98,13 @@ public ChannelActionImpl(Class clazz, String name, Guild guild, ChannelType t this.name = name; } + @Nonnull + @Override + public ChannelActionImpl reason(@Nullable String reason) + { + return (ChannelActionImpl) super.reason(reason); + } + @Nonnull @Override public ChannelActionImpl setCheck(BooleanSupplier checks) @@ -121,7 +146,7 @@ public ChannelType getType() public ChannelActionImpl setName(@Nonnull String name) { Checks.notEmpty(name, "Name"); - Checks.notLonger(name, 100, "Name"); + Checks.notLonger(name, Channel.MAX_NAME_LENGTH, "Name"); this.name = name; return this; } @@ -154,10 +179,14 @@ public ChannelActionImpl setPosition(Integer position) @CheckReturnValue public ChannelActionImpl setTopic(String topic) { - if (type != ChannelType.TEXT && type != ChannelType.NEWS) - throw new UnsupportedOperationException("Can only set the topic for a TextChannel or NewsChannel!"); - if (topic != null && topic.length() > 1024) - throw new IllegalArgumentException("Channel Topic must not be greater than 1024 in length!"); + Checks.checkSupportedChannelTypes(TOPIC_SUPPORTED, type, "Topic"); + if (topic != null) + { + if (type == ChannelType.FORUM) + Checks.notLonger(topic, ForumChannel.MAX_FORUM_TOPIC_LENGTH, "Topic"); + else + Checks.notLonger(topic, StandardGuildMessageChannel.MAX_TOPIC_LENGTH, "Topic"); + } this.topic = topic; return this; } @@ -167,8 +196,7 @@ public ChannelActionImpl setTopic(String topic) @CheckReturnValue public ChannelActionImpl setNSFW(boolean nsfw) { - if (type != ChannelType.TEXT && type != ChannelType.NEWS) - throw new UnsupportedOperationException("Can only set nsfw for a TextChannel or NewsChannel!"); + Checks.checkSupportedChannelTypes(NSFW_SUPPORTED, type, "NSFW (age-restricted)"); this.nsfw = nsfw; return this; } @@ -178,13 +206,33 @@ public ChannelActionImpl setNSFW(boolean nsfw) @CheckReturnValue public ChannelActionImpl setSlowmode(int slowmode) { - if (type != ChannelType.TEXT) - throw new UnsupportedOperationException("Can only set slowmode on text channels"); - Checks.check(slowmode <= TextChannel.MAX_SLOWMODE && slowmode >= 0, "Slowmode must be between 0 and %d (seconds)!", TextChannel.MAX_SLOWMODE); + Checks.checkSupportedChannelTypes(SLOWMODE_SUPPORTED, type, "Slowmode"); + Checks.check(slowmode <= ISlowmodeChannel.MAX_SLOWMODE && slowmode >= 0, "Slowmode must be between 0 and %d (seconds)!", ISlowmodeChannel.MAX_SLOWMODE); this.slowmode = slowmode; return this; } + @Nonnull + @Override + public ChannelAction setDefaultReaction(@Nullable Emoji emoji) + { + if (type != ChannelType.FORUM) + throw new UnsupportedOperationException("Can only set default reaction emoji on a ForumChannel!"); + this.defaultReactionEmoji = emoji; + return this; + } + + @Nonnull + @Override + public ChannelAction setAvailableTags(@Nonnull List tags) + { + if (type != ChannelType.FORUM) + throw new UnsupportedOperationException("Can only set available tags on a ForumChannel!"); + Checks.noneNull(tags, "Tags"); + this.availableTags = new ArrayList<>(tags); + return this; + } + @Nonnull @Override @CheckReturnValue @@ -279,7 +327,6 @@ private ChannelActionImpl addOverride(long targetId, int type, long allow, lo return this; } - // --voice only-- @Nonnull @Override @CheckReturnValue @@ -338,16 +385,24 @@ protected RequestBody finalizeData() if (parent != null) object.put("parent_id", parent.getId()); - //Text only + //Text and Forum if (slowmode != null) object.put("rate_limit_per_user", slowmode); - //Text and News + //Text, Forum, and News if (topic != null && !topic.isEmpty()) object.put("topic", topic); if (nsfw != null) object.put("nsfw", nsfw); + //Forum only + if (defaultReactionEmoji instanceof CustomEmoji) + object.put("default_reaction_emoji", DataObject.empty().put("emoji_id", ((CustomEmoji) defaultReactionEmoji).getId())); + else if (defaultReactionEmoji instanceof UnicodeEmoji) + object.put("default_reaction_emoji", DataObject.empty().put("emoji_name", defaultReactionEmoji.getName())); + if (availableTags != null) + object.put("available_tags", DataArray.fromCollection(availableTags)); + //Voice only if (userlimit != null) object.put("user_limit", userlimit); @@ -383,6 +438,9 @@ protected void handleSuccess(Response response, Request request) case CATEGORY: channel = builder.createCategory(response.getObject(), guild.getIdLong()); break; + case FORUM: + channel = builder.createForumChannel(response.getObject(), guild.getIdLong()); + break; default: request.onFailure(new IllegalStateException("Created channel of unknown type!")); return; diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ForumPostActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ForumPostActionImpl.java new file mode 100644 index 0000000000..79f34ef327 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ForumPostActionImpl.java @@ -0,0 +1,171 @@ +/* + * 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.internal.requests.restaction; + +import gnu.trove.set.TLongSet; +import gnu.trove.set.hash.TLongHashSet; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumPost; +import net.dv8tion.jda.api.entities.channel.forums.ForumTagSnowflake; +import net.dv8tion.jda.api.requests.Request; +import net.dv8tion.jda.api.requests.Response; +import net.dv8tion.jda.api.requests.restaction.ForumPostAction; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.dv8tion.jda.internal.entities.EntityBuilder; +import net.dv8tion.jda.internal.requests.RestActionImpl; +import net.dv8tion.jda.internal.requests.Route; +import net.dv8tion.jda.internal.utils.Checks; +import net.dv8tion.jda.internal.utils.message.MessageCreateBuilderMixin; +import okhttp3.RequestBody; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.function.BooleanSupplier; + +public class ForumPostActionImpl extends RestActionImpl implements ForumPostAction, MessageCreateBuilderMixin +{ + private final MessageCreateBuilder builder; + private final ForumChannel channel; + private final TLongSet appliedTags = new TLongHashSet(); + private String name; + private ThreadChannel.AutoArchiveDuration autoArchiveDuration; + + public ForumPostActionImpl(ForumChannel channel, String name, MessageCreateBuilder builder) + { + super(channel.getJDA(), Route.Channels.CREATE_THREAD.compile(channel.getId())); + this.builder = builder; + this.channel = channel; + setName(name); + } + + @Nonnull + @Override + public ForumPostAction setCheck(BooleanSupplier checks) + { + return (ForumPostAction) super.setCheck(checks); + } + + @Nonnull + @Override + public ForumPostAction addCheck(@Nonnull BooleanSupplier checks) + { + return (ForumPostAction) super.addCheck(checks); + } + + @Nonnull + @Override + public ForumPostAction deadline(long timestamp) + { + return (ForumPostAction) super.deadline(timestamp); + } + + @Nonnull + @Override + public Guild getGuild() + { + return channel.getGuild(); + } + + @Nonnull + @Override + public ForumChannel getChannel() + { + return channel; + } + + @Nonnull + @Override + public ForumPostAction setTags(@Nonnull Collection tags) + { + Checks.noneNull(tags, "Tags"); + Checks.check(tags.size() <= ForumChannel.MAX_POST_TAGS, "Provided more than %d tags.", ForumChannel.MAX_POST_TAGS); + Checks.check(!channel.isTagRequired() || !tags.isEmpty(), "This forum requires at least one tag per post! See ForumChannel#isRequireTag()"); + this.appliedTags.clear(); + tags.forEach(t -> this.appliedTags.add(t.getIdLong())); + return this; + } + + @Nonnull + @Override + public ChannelType getType() + { + return ChannelType.GUILD_PUBLIC_THREAD; + } + + @Nonnull + @Override + public ForumPostAction setName(@Nonnull String name) + { + Checks.notEmpty(name, "Name"); + Checks.notLonger(name, Channel.MAX_NAME_LENGTH, "Name"); + this.name = name.trim(); + return this; + } + + @Nonnull + @Override + public ForumPostAction setAutoArchiveDuration(@Nonnull ThreadChannel.AutoArchiveDuration autoArchiveDuration) + { + Checks.notNull(autoArchiveDuration, "AutoArchiveDuration"); + this.autoArchiveDuration = autoArchiveDuration; + return this; + } + + @Override + public MessageCreateBuilder getBuilder() + { + return builder; + } + + @Override + protected RequestBody finalizeData() + { + try (MessageCreateData message = builder.build()) + { + DataObject json = DataObject.empty(); + json.put("message", message); + json.put("name", name); + if (autoArchiveDuration != null) + json.put("auto_archive_duration", autoArchiveDuration.getMinutes()); + if (!appliedTags.isEmpty()) + json.put("applied_tags", appliedTags.toArray()); + else if (getChannel().isTagRequired()) + throw new IllegalStateException("Cannot create posts without a tag in this forum. Apply at least one tag!"); + return getMultipartBody(message.getFiles(), json); + } + } + + @Override + protected void handleSuccess(Response response, Request request) + { + DataObject json = response.getObject(); + + EntityBuilder entityBuilder = api.getEntityBuilder(); + + ThreadChannel thread = entityBuilder.createThreadChannel(json, getGuild().getIdLong()); + Message message = entityBuilder.createMessageWithChannel(json.getObject("message"), thread, false); + + request.onSuccess(new ForumPost(message, thread)); + } +} diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ThreadChannelActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ThreadChannelActionImpl.java index ae66e44bcf..a977717527 100644 --- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ThreadChannelActionImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ThreadChannelActionImpl.java @@ -17,6 +17,7 @@ package net.dv8tion.jda.internal.requests.restaction; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; @@ -45,7 +46,7 @@ public class ThreadChannelActionImpl extends AuditableRestActionImpl> req threadObj.put("member", selfThreadMemberObj); } - ThreadChannel thread = builder.createThreadChannel(threadObj, getGuild().getIdLong()); - list.add(thread); + try + { + ThreadChannel thread = builder.createThreadChannel(threadObj, getGuild().getIdLong()); + list.add(thread); - if (this.useCache) - this.cached.add(thread); - this.last = thread; - this.lastKey = last.getIdLong(); + if (this.useCache) + this.cached.add(thread); + this.last = thread; + this.lastKey = last.getIdLong(); + } + catch (Exception e) + { + if (EntityBuilder.MISSING_CHANNEL.equals(e.getMessage())) + EntityBuilder.LOG.debug("Discarding thread without cached parent channel. JSON: {}", threadObj); + else + EntityBuilder.LOG.warn("Failed to create thread channel. JSON: {}", threadObj, e); + } } catch (ParsingException | NullPointerException e) { diff --git a/src/main/java/net/dv8tion/jda/internal/utils/Checks.java b/src/main/java/net/dv8tion/jda/internal/utils/Checks.java index b1fb73d3e6..e20c2d3407 100644 --- a/src/main/java/net/dv8tion/jda/internal/utils/Checks.java +++ b/src/main/java/net/dv8tion/jda/internal/utils/Checks.java @@ -18,6 +18,7 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.IPermissionHolder; +import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.exceptions.MissingAccessException; @@ -286,4 +287,12 @@ public static void checkAccess(IPermissionHolder issuer, GuildChannel channel) throw new MissingAccessException(channel, Permission.VOICE_CONNECT); throw new MissingAccessException(channel, Permission.VIEW_CHANNEL); } + + // Type checks + + public static void checkSupportedChannelTypes(EnumSet supported, ChannelType type, String what) + { + Checks.check(supported.contains(type), "Can only configure %s for channels of types %s", what, + JDALogger.getLazyString(() -> supported.stream().map(ChannelType::name).collect(Collectors.joining(", ")))); + } }