11package net .discordjug .javabot .systems .help .commands ;
22
3+ import java .util .List ;
4+
5+ import org .jetbrains .annotations .NotNull ;
6+
7+ import net .discordjug .javabot .annotations .AutoDetectableComponentHandler ;
38import net .discordjug .javabot .data .config .BotConfig ;
49import net .discordjug .javabot .data .h2db .DbActions ;
510import net .discordjug .javabot .systems .help .HelpManager ;
611import net .discordjug .javabot .systems .help .dao .HelpAccountRepository ;
712import net .discordjug .javabot .systems .help .dao .HelpTransactionRepository ;
813import net .discordjug .javabot .systems .user_preferences .UserPreferenceService ;
14+ import net .discordjug .javabot .util .ExceptionLogger ;
915import net .discordjug .javabot .util .Responses ;
1016import net .dv8tion .jda .api .entities .channel .ChannelType ;
1117import net .dv8tion .jda .api .entities .channel .concrete .ThreadChannel ;
18+ import net .dv8tion .jda .api .entities .channel .unions .MessageChannelUnion ;
19+ import net .dv8tion .jda .api .events .interaction .ModalInteractionEvent ;
1220import net .dv8tion .jda .api .events .interaction .command .SlashCommandInteractionEvent ;
13- import net .dv8tion .jda .api .interactions .commands .CommandInteraction ;
21+ import net .dv8tion .jda .api .interactions .Interaction ;
22+ import net .dv8tion .jda .api .interactions .callbacks .IReplyCallback ;
1423import net .dv8tion .jda .api .interactions .commands .OptionMapping ;
1524import net .dv8tion .jda .api .interactions .commands .OptionType ;
1625import net .dv8tion .jda .api .interactions .commands .build .Commands ;
17-
18- import org .jetbrains .annotations .NotNull ;
26+ import net .dv8tion .jda .api .interactions .components .ActionRow ;
27+ import net .dv8tion .jda .api .interactions .components .text .TextInput ;
28+ import net .dv8tion .jda .api .interactions .components .text .TextInputStyle ;
29+ import net .dv8tion .jda .api .interactions .modals .Modal ;
30+ import net .dv8tion .jda .api .interactions .modals .ModalMapping ;
1931import xyz .dynxsty .dih4jda .interactions .commands .application .SlashCommand ;
32+ import xyz .dynxsty .dih4jda .interactions .components .ModalHandler ;
33+ import xyz .dynxsty .dih4jda .util .ComponentIdBuilder ;
2034
2135/**
2236 * A simple command that can be used inside reserved help channels to
2337 * immediately unreserve them, instead of waiting for a timeout.
2438 */
25- public class UnreserveCommand extends SlashCommand {
39+ @ AutoDetectableComponentHandler (UnreserveCommand .UNRESERVE_ID )
40+ public class UnreserveCommand extends SlashCommand implements ModalHandler {
41+ static final String UNRESERVE_ID = "unreserve" ;
42+ private static final int MINIMUM_REASON_LENGTH = 11 ;
43+ private static final String REASON_ID = "reason" ;
2644 private final BotConfig botConfig ;
2745 private final DbActions dbActions ;
2846 private final HelpAccountRepository helpAccountRepository ;
@@ -43,35 +61,80 @@ public UnreserveCommand(BotConfig botConfig, DbActions dbActions, HelpTransactio
4361 this .helpAccountRepository = helpAccountRepository ;
4462 this .helpTransactionRepository = helpTransactionRepository ;
4563 this .preferenceService = preferenceService ;
46- setCommandData (Commands .slash ("unreserve" , "Unreserves this post marking your question/issue as resolved." )
64+ setCommandData (Commands .slash (UNRESERVE_ID , "Unreserves this post marking your question/issue as resolved." )
4765 .setGuildOnly (true )
48- .addOption (OptionType .STRING , "reason" , "The reason why you're unreserving this channel" , false )
66+ .addOption (OptionType .STRING , REASON_ID , "The reason why you're unreserving this channel" , false )
4967 );
5068 }
5169
5270 @ Override
5371 public void execute (@ NotNull SlashCommandInteractionEvent event ) {
72+ String reason = event .getOption (REASON_ID , null , OptionMapping ::getAsString );
73+ onCloseRequest (event , event , event .getChannel (), reason , ()->{
74+ TextInput reasonInput = TextInput
75+ .create (REASON_ID , "Reason" , TextInputStyle .SHORT )
76+ .setRequiredRange (MINIMUM_REASON_LENGTH , 100 )
77+ .setRequired (true )
78+ .setPlaceholder (reason == null ? "Please enter the reason you are closing this post here" : reason )
79+ .build ();
80+ Modal modal = Modal
81+ .create (ComponentIdBuilder .build (UNRESERVE_ID ), "Close post" )
82+ .addComponents (ActionRow .of (
83+ reasonInput ))
84+ .build ();
85+ event .replyModal (modal ).queue ();
86+ });
87+ }
88+
89+ @ Override
90+ public void handleModal (ModalInteractionEvent event , List <ModalMapping > values ) {
91+ values
92+ .stream ()
93+ .filter (mapping -> REASON_ID .equals (mapping .getId ()))
94+ .map (mapping -> mapping .getAsString ())
95+ .filter (reason -> !isReasonInvalid (reason ))
96+ .findAny ()
97+ .ifPresentOrElse (reason -> {
98+ onCloseRequest (event , event , event .getChannel (), reason , ()->{
99+ Responses .error (event , "The provided reason is missing or not valid" ).queue ();
100+ ExceptionLogger .capture (new IllegalStateException ("A reason was expected but not present" ), getClass ().getName ());
101+ });
102+ }, () -> Responses .warning (event , "A valid reason must be provided" ).queue ());
103+
104+ }
105+
106+ private void onCloseRequest (Interaction interaction , IReplyCallback replyCallback , MessageChannelUnion channel , String reason , Runnable noReasonHandler ) {
107+ ChannelType channelType = channel .getType ();
54108 // check whether the channel type is either text or thread (possible forum post?)
55- if (event . getChannelType () != ChannelType .TEXT && event . getChannelType () != ChannelType .GUILD_PUBLIC_THREAD ) {
56- replyInvalidChannel (event );
109+ if (channelType != ChannelType .TEXT && channelType != ChannelType .GUILD_PUBLIC_THREAD ) {
110+ replyInvalidChannel (replyCallback );
57111 return ;
58112 }
59- ThreadChannel postThread = event . getChannel () .asThreadChannel ();
113+ ThreadChannel postThread = channel .asThreadChannel ();
60114 if (postThread .getParentChannel ().getType () != ChannelType .FORUM ) {
61- replyInvalidChannel (event );
115+ replyInvalidChannel (replyCallback );
116+ return ;
62117 }
63118 HelpManager manager = new HelpManager (postThread , dbActions , botConfig , helpAccountRepository , helpTransactionRepository , preferenceService );
64- if (manager .isForumEligibleToBeUnreserved (event .getInteraction ())) {
65- manager .close (event ,
66- event .getUser ().getIdLong () == manager .getPostThread ().getOwnerIdLong (),
67- event .getOption ("reason" , null , OptionMapping ::getAsString ));
119+ if (manager .isForumEligibleToBeUnreserved (interaction )) {
120+ if (replyCallback .getUser ().getIdLong () != postThread .getOwnerIdLong () && isReasonInvalid (reason )) {
121+ noReasonHandler .run ();
122+ return ;
123+ }
124+ manager .close (replyCallback ,
125+ replyCallback .getUser ().getIdLong () == manager .getPostThread ().getOwnerIdLong (),
126+ reason );
68127 } else {
69- Responses .warning (event , "Could not close this post" , "You're not allowed to close this post." ).queue ();
128+ Responses .warning (replyCallback , "Could not close this post" , "You're not allowed to close this post." ).queue ();
70129 }
71130 }
72131
73- private void replyInvalidChannel (CommandInteraction interaction ) {
74- Responses .warning (interaction , "Invalid Channel" ,
132+ private boolean isReasonInvalid (String reason ) {
133+ return reason == null || reason .length () < MINIMUM_REASON_LENGTH ;
134+ }
135+
136+ private void replyInvalidChannel (IReplyCallback replyCallback ) {
137+ Responses .warning (replyCallback , "Invalid Channel" ,
75138 "This command may only be used in either the text-channel-based help system, or in our new forum help system." )
76139 .queue ();
77140 }
0 commit comments