Skip to content

Commit 41c3edd

Browse files
feat: create application form command
Co-authored-by: Suraj Kumar <[email protected]>
1 parent 79a21ca commit 41c3edd

File tree

4 files changed

+443
-0
lines changed

4 files changed

+443
-0
lines changed

application/src/main/java/org/togetherjava/tjbot/features/Features.java

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.togetherjava.tjbot.features.moderation.temp.TemporaryModerationRoutine;
6565
import org.togetherjava.tjbot.features.reminder.RemindRoutine;
6666
import org.togetherjava.tjbot.features.reminder.ReminderCommand;
67+
import org.togetherjava.tjbot.features.roleapplication.ApplicationCreateCommand;
6768
import org.togetherjava.tjbot.features.system.BotCore;
6869
import org.togetherjava.tjbot.features.system.LogLevelCommand;
6970
import org.togetherjava.tjbot.features.tags.TagCommand;
@@ -192,6 +193,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
192193
features.add(new BookmarksCommand(bookmarksSystem));
193194
features.add(new ChatGptCommand(chatGptService, helpSystemHelper));
194195
features.add(new JShellCommand(jshellEval));
196+
features.add(new ApplicationCreateCommand(config));
195197

196198
FeatureBlacklist<Class<?>> blacklist = blacklistConfig.normal();
197199
return blacklist.filterStream(features.stream(), Object::getClass).toList();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package org.togetherjava.tjbot.features.roleapplication;
2+
3+
import com.github.benmanes.caffeine.cache.Cache;
4+
import com.github.benmanes.caffeine.cache.Caffeine;
5+
import net.dv8tion.jda.api.EmbedBuilder;
6+
import net.dv8tion.jda.api.entities.Guild;
7+
import net.dv8tion.jda.api.entities.Member;
8+
import net.dv8tion.jda.api.entities.MessageEmbed;
9+
import net.dv8tion.jda.api.entities.User;
10+
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
11+
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
12+
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
13+
14+
import org.togetherjava.tjbot.config.ApplicationFormConfig;
15+
16+
import java.time.Duration;
17+
import java.time.Instant;
18+
import java.time.OffsetDateTime;
19+
import java.util.List;
20+
import java.util.Optional;
21+
import java.util.function.Predicate;
22+
import java.util.regex.Pattern;
23+
24+
/**
25+
* Handles the actual process of submitting role applications.
26+
* <p>
27+
* This class is responsible for managing application submissions via modal interactions, ensuring
28+
* that submissions are sent to the appropriate application channel, and enforcing cooldowns for
29+
* users to prevent spamming.
30+
*/
31+
public class ApplicationApplyHandler {
32+
33+
private final Cache<Member, OffsetDateTime> applicationSubmitCooldown;
34+
private final Predicate<String> applicationChannelPattern;
35+
private final ApplicationFormConfig formConfig;
36+
37+
/**
38+
* Constructs a new {@code ApplicationApplyHandler} instance.
39+
*
40+
* @param formConfig the configuration that contains the details for the application form
41+
* including the cooldown duration and channel pattern.
42+
*/
43+
public ApplicationApplyHandler(ApplicationFormConfig formConfig) {
44+
this.formConfig = formConfig;
45+
this.applicationChannelPattern =
46+
Pattern.compile(formConfig.applicationChannelPattern()).asMatchPredicate();
47+
48+
final Duration applicationSubmitCooldownDuration =
49+
Duration.ofMinutes(formConfig.applicationSubmitCooldownMinutes());
50+
applicationSubmitCooldown =
51+
Caffeine.newBuilder().expireAfterWrite(applicationSubmitCooldownDuration).build();
52+
}
53+
54+
/**
55+
* Sends the result of an application submission to the designated application channel in the
56+
* guild.
57+
* <p>
58+
* The {@code args} parameter should contain the applicant's name and the role they are applying
59+
* for.
60+
*
61+
* @param event the modal interaction event triggering the application submission
62+
* @param args the arguments provided in the application submission
63+
* @param answer the answer provided by the applicant to the default question
64+
*/
65+
protected void sendApplicationResult(final ModalInteractionEvent event, List<String> args,
66+
String answer) {
67+
Guild guild = event.getGuild();
68+
if (args.size() != 2 || guild == null) {
69+
return;
70+
}
71+
72+
Optional<TextChannel> applicationChannel = getApplicationChannel(guild);
73+
if (applicationChannel.isEmpty()) {
74+
return;
75+
}
76+
77+
User applicant = event.getUser();
78+
EmbedBuilder embed =
79+
new EmbedBuilder().setAuthor(applicant.getName(), null, applicant.getAvatarUrl())
80+
.setColor(ApplicationCreateCommand.AMBIENT_COLOR)
81+
.setTimestamp(Instant.now())
82+
.setFooter("Submitted at");
83+
84+
String roleString = args.getLast();
85+
MessageEmbed.Field roleField = new MessageEmbed.Field("Role", roleString, false);
86+
embed.addField(roleField);
87+
88+
MessageEmbed.Field answerField =
89+
new MessageEmbed.Field(formConfig.defaultQuestion(), answer, false);
90+
embed.addField(answerField);
91+
92+
applicationChannel.get().sendMessageEmbeds(embed.build()).queue();
93+
}
94+
95+
/**
96+
* Retrieves the application channel from the given {@link Guild}.
97+
*
98+
* @param guild the guild from which to retrieve the application channel
99+
* @return an {@link Optional} containing the {@link TextChannel} representing the application
100+
* channel, or an empty {@link Optional} if no such channel is found
101+
*/
102+
private Optional<TextChannel> getApplicationChannel(Guild guild) {
103+
return guild.getChannels()
104+
.stream()
105+
.filter(channel -> applicationChannelPattern.test(channel.getName()))
106+
.filter(channel -> channel.getType().isMessage())
107+
.map(TextChannel.class::cast)
108+
.findFirst();
109+
}
110+
111+
public Cache<Member, OffsetDateTime> getApplicationSubmitCooldown() {
112+
return applicationSubmitCooldown;
113+
}
114+
115+
protected void submitApplicationFromModalInteraction(ModalInteractionEvent event,
116+
List<String> args) {
117+
Guild guild = event.getGuild();
118+
119+
if (guild == null) {
120+
return;
121+
}
122+
123+
ModalMapping modalAnswer = event.getValues().getFirst();
124+
125+
sendApplicationResult(event, args, modalAnswer.getAsString());
126+
event.reply("Your application has been submitted. Thank you for applying! 😎")
127+
.setEphemeral(true)
128+
.queue();
129+
130+
applicationSubmitCooldown.put(event.getMember(), OffsetDateTime.now());
131+
}
132+
}

0 commit comments

Comments
 (0)