Skip to content

Commit 41a827f

Browse files
feat(DynamicVoiceChat): implement main logic
Co-authored-by: Suraj Kumar <76599223+surajkumar@users.noreply.github.com> Signed-off-by: Chris Sdogkos <work@chris-sdogkos.com>
1 parent 5bc588e commit 41a827f

File tree

4 files changed

+132
-2
lines changed

4 files changed

+132
-2
lines changed

application/config.json.template

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,5 +199,10 @@
199199
"rolePattern": "Top Helper.*",
200200
"assignmentChannelPattern": "community-commands",
201201
"announcementChannelPattern": "hall-of-fame"
202-
}
202+
},
203+
"dynamicVoiceChannelPatterns": [
204+
"Gaming",
205+
"Support/Studying Room",
206+
"Chit Chat"
207+
]
203208
}

application/src/main/java/org/togetherjava/tjbot/config/Config.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public final class Config {
4949
private final String selectRolesChannelPattern;
5050
private final String memberCountCategoryPattern;
5151
private final TopHelpersConfig topHelpers;
52+
private final List<String> dynamicVoiceChannelPatterns;
5253

5354
@SuppressWarnings("ConstructorWithTooManyParameters")
5455
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
@@ -102,7 +103,9 @@ private Config(@JsonProperty(value = "token", required = true) String token,
102103
@JsonProperty(value = "rssConfig", required = true) RSSFeedsConfig rssFeedsConfig,
103104
@JsonProperty(value = "selectRolesChannelPattern",
104105
required = true) String selectRolesChannelPattern,
105-
@JsonProperty(value = "topHelpers", required = true) TopHelpersConfig topHelpers) {
106+
@JsonProperty(value = "topHelpers", required = true) TopHelpersConfig topHelpers,
107+
@JsonProperty(value = "dynamicVoiceChannelPatterns",
108+
required = true) List<String> dynamicVoiceChannelPatterns) {
106109
this.token = Objects.requireNonNull(token);
107110
this.githubApiKey = Objects.requireNonNull(githubApiKey);
108111
this.databasePath = Objects.requireNonNull(databasePath);
@@ -138,6 +141,7 @@ private Config(@JsonProperty(value = "token", required = true) String token,
138141
this.rssFeedsConfig = Objects.requireNonNull(rssFeedsConfig);
139142
this.selectRolesChannelPattern = Objects.requireNonNull(selectRolesChannelPattern);
140143
this.topHelpers = Objects.requireNonNull(topHelpers);
144+
this.dynamicVoiceChannelPatterns = Objects.requireNonNull(dynamicVoiceChannelPatterns);
141145
}
142146

143147
/**
@@ -457,4 +461,13 @@ public RSSFeedsConfig getRSSFeedsConfig() {
457461
public TopHelpersConfig getTopHelpers() {
458462
return topHelpers;
459463
}
464+
465+
/**
466+
* Gets the list of voice channel patterns that are treated dynamically.
467+
*
468+
* @return the list of dynamic voice channel patterns
469+
*/
470+
public List<String> getDynamicVoiceChannelPatterns() {
471+
return dynamicVoiceChannelPatterns;
472+
}
460473
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import org.togetherjava.tjbot.features.tophelper.TopHelpersMessageListener;
7878
import org.togetherjava.tjbot.features.tophelper.TopHelpersPurgeMessagesRoutine;
7979
import org.togetherjava.tjbot.features.tophelper.TopHelpersService;
80+
import org.togetherjava.tjbot.features.voicechat.DynamicVoiceChat;
8081

8182
import java.util.ArrayList;
8283
import java.util.Collection;
@@ -161,6 +162,9 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
161162
features.add(new SlashCommandEducator());
162163
features.add(new PinnedNotificationRemover(config));
163164

165+
// Voice receivers
166+
features.add(new DynamicVoiceChat(config));
167+
164168
// Event receivers
165169
features.add(new RejoinModerationRoleListener(actionsStore, config));
166170
features.add(new GuildLeaveCloseThreadListener(config));
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.togetherjava.tjbot.features.voicechat;
2+
3+
import net.dv8tion.jda.api.EmbedBuilder;
4+
import net.dv8tion.jda.api.entities.Guild;
5+
import net.dv8tion.jda.api.entities.Member;
6+
import net.dv8tion.jda.api.entities.MessageEmbed;
7+
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
8+
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
9+
import net.dv8tion.jda.api.entities.channel.unions.AudioChannelUnion;
10+
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
11+
import org.jetbrains.annotations.NotNull;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
import org.togetherjava.tjbot.config.Config;
16+
import org.togetherjava.tjbot.features.VoiceReceiverAdapter;
17+
18+
import java.util.List;
19+
import java.util.regex.Pattern;
20+
21+
public class DynamicVoiceChat extends VoiceReceiverAdapter {
22+
private static final Logger logger = LoggerFactory.getLogger(DynamicVoiceChat.class);
23+
private final List<Pattern> dynamicVoiceChannelPatterns;
24+
25+
public DynamicVoiceChat(Config config) {
26+
this.dynamicVoiceChannelPatterns =
27+
config.getDynamicVoiceChannelPatterns().stream().map(Pattern::compile).toList();
28+
}
29+
30+
@Override
31+
public void onVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) {
32+
AudioChannelUnion channelJoined = event.getChannelJoined();
33+
AudioChannelUnion channelLeft = event.getChannelLeft();
34+
35+
if (channelJoined != null && eventHappenOnDynamicRootChannel(channelJoined)) {
36+
logger.debug("Event happened on joined channel {}", channelJoined);
37+
createDynamicVoiceChannel(event, channelJoined.asVoiceChannel());
38+
}
39+
40+
if (channelLeft != null && !eventHappenOnDynamicRootChannel(channelLeft)) {
41+
logger.debug("Event happened on left channel {}", channelLeft);
42+
deleteDynamicVoiceChannel(channelLeft);
43+
}
44+
}
45+
46+
private boolean eventHappenOnDynamicRootChannel(AudioChannelUnion channel) {
47+
return dynamicVoiceChannelPatterns.stream()
48+
.anyMatch(pattern -> pattern.matcher(channel.getName()).matches());
49+
}
50+
51+
private void createDynamicVoiceChannel(@NotNull GuildVoiceUpdateEvent event,
52+
VoiceChannel channel) {
53+
Guild guild = event.getGuild();
54+
Member member = event.getMember();
55+
String newChannelName = "%s's %s".formatted(member.getEffectiveName(), channel.getName());
56+
57+
channel.createCopy()
58+
.setName(newChannelName)
59+
.setPosition(channel.getPositionRaw())
60+
.onSuccess(newChannel -> {
61+
moveMember(guild, member, newChannel);
62+
sendWarningEmbed(newChannel);
63+
})
64+
.queue(newChannel -> logger.info("Successfully created {} voice channel.",
65+
newChannel.getName()),
66+
error -> logger.error("Failed to create dynamic voice channel", error));
67+
}
68+
69+
private void moveMember(Guild guild, Member member, AudioChannel channel) {
70+
guild.moveVoiceMember(member, channel)
71+
.queue(_ -> logger.info(
72+
"Successfully moved {} to newly created dynamic voice channel {}",
73+
member.getEffectiveName(), channel.getName()),
74+
error -> logger.error(
75+
"Failed to move user into dynamically created voice channel {}, {}",
76+
member.getNickname(), channel.getName(), error));
77+
}
78+
79+
private void deleteDynamicVoiceChannel(AudioChannelUnion channel) {
80+
int memberCount = channel.getMembers().size();
81+
82+
if (memberCount > 0) {
83+
logger.debug("Voice channel {} not empty ({} members), so not removing.",
84+
channel.getName(), memberCount);
85+
return;
86+
}
87+
88+
channel.delete()
89+
.queue(_ -> logger.info("Deleted dynamically created voice channel: {} ",
90+
channel.getName()),
91+
error -> logger.error("Failed to delete dynamically created voice channel: {} ",
92+
channel.getName(), error));
93+
}
94+
95+
private void sendWarningEmbed(VoiceChannel channel) {
96+
MessageEmbed messageEmbed = new EmbedBuilder()
97+
.addField("👋 Heads up!",
98+
"""
99+
This is a **temporary** voice chat channel. Messages sent here will be *cleared* once \
100+
the channel is deleted when everyone leaves. If you need to keep something important, \
101+
make sure to save it elsewhere. 💬
102+
""",
103+
false)
104+
.build();
105+
106+
channel.sendMessageEmbeds(messageEmbed).queue();
107+
}
108+
}

0 commit comments

Comments
 (0)