44import net .dv8tion .jda .api .entities .Guild ;
55import net .dv8tion .jda .api .entities .Member ;
66import net .dv8tion .jda .api .entities .MessageEmbed ;
7+ import net .dv8tion .jda .api .entities .MessageHistory ;
8+ import net .dv8tion .jda .api .entities .channel .concrete .Category ;
79import net .dv8tion .jda .api .entities .channel .concrete .VoiceChannel ;
810import net .dv8tion .jda .api .entities .channel .middleman .AudioChannel ;
911import net .dv8tion .jda .api .entities .channel .unions .AudioChannelUnion ;
1012import net .dv8tion .jda .api .events .guild .voice .GuildVoiceUpdateEvent ;
13+ import net .dv8tion .jda .api .managers .channel .middleman .AudioChannelManager ;
14+ import net .dv8tion .jda .api .requests .RestAction ;
1115import org .jetbrains .annotations .NotNull ;
1216import org .slf4j .Logger ;
1317import org .slf4j .LoggerFactory ;
1620import org .togetherjava .tjbot .features .VoiceReceiverAdapter ;
1721
1822import java .util .List ;
23+ import java .util .Optional ;
1924import java .util .regex .Pattern ;
2025
2126/**
2227 * Handles dynamic voice channel creation and deletion based on user activity.
2328 * <p>
2429 * When a member joins a configured root channel, a temporary copy is created and the member is
25- * moved into it. Once the channel becomes empty, it is deleted.
30+ * moved into it. Once the channel becomes empty, it is archived and further deleted using a
31+ * {@link VoiceChatCleanupStrategy}.
2632 */
2733public final class DynamicVoiceChat extends VoiceReceiverAdapter {
2834 private static final Logger logger = LoggerFactory .getLogger (DynamicVoiceChat .class );
35+
36+ // @christolis: Unless somebody is willing to make the category name configurable,
37+ // I will leave this here as a constant since I don't see a justification for naming
38+ // this category name into something different.
39+ private static final String ARCHIVE_CATEGORY_NAME = "Voice Channel Archives" ;
40+ private static final int CLEAN_CHANNELS_AMOUNT = 2 ;
41+ private static final int MINIMUM_CHANNELS_AMOUNT = 3 ;
42+
43+ private final VoiceChatCleanupStrategy voiceChatCleanupStrategy ;
2944 private final List <Pattern > dynamicVoiceChannelPatterns ;
3045
3146 public DynamicVoiceChat (Config config ) {
3247 this .dynamicVoiceChannelPatterns =
3348 config .getDynamicVoiceChannelPatterns ().stream ().map (Pattern ::compile ).toList ();
49+ this .voiceChatCleanupStrategy =
50+ new OldestVoiceChatCleanup (CLEAN_CHANNELS_AMOUNT , MINIMUM_CHANNELS_AMOUNT );
3451 }
3552
3653 @ Override
@@ -45,7 +62,17 @@ public void onVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) {
4562
4663 if (channelLeft != null && !eventHappenOnDynamicRootChannel (channelLeft )) {
4764 logger .debug ("Event happened on left channel {}" , channelLeft );
48- deleteDynamicVoiceChannel (channelLeft );
65+
66+ MessageHistory messageHistory = channelLeft .asVoiceChannel ().getHistory ();
67+ messageHistory .retrievePast (2 ).queue (messages -> {
68+ // Don't forget that there is always one
69+ // embed message sent by the bot every time.
70+ if (messages .size () > 1 ) {
71+ archiveDynamicVoiceChannel (channelLeft );
72+ } else {
73+ channelLeft .delete ().queue ();
74+ }
75+ });
4976 }
5077 }
5178
@@ -82,20 +109,40 @@ private void moveMember(Guild guild, Member member, AudioChannel channel) {
82109 member .getNickname (), channel .getName (), error ));
83110 }
84111
85- private void deleteDynamicVoiceChannel (AudioChannelUnion channel ) {
112+ private void archiveDynamicVoiceChannel (AudioChannelUnion channel ) {
86113 int memberCount = channel .getMembers ().size ();
114+ String channelName = channel .getName ();
87115
88116 if (memberCount > 0 ) {
89- logger .debug ("Voice channel {} not empty ({} members), so not removing." ,
90- channel .getName (), memberCount );
117+ logger .debug ("Voice channel {} not empty ({} members), so not removing." , channelName ,
118+ memberCount );
119+ return ;
120+ }
121+
122+ Optional <Category > archiveCategoryOptional = channel .getGuild ()
123+ .getCategoryCache ()
124+ .stream ()
125+ .filter (c -> c .getName ().equalsIgnoreCase (ARCHIVE_CATEGORY_NAME ))
126+ .findFirst ();
127+
128+ AudioChannelManager <?, ?> channelManager = channel .getManager ();
129+ RestAction <Void > restActionChain =
130+ channelManager .setName (String .format ("%s (Archived)" , channelName ))
131+ .and (channel .getPermissionContainer ().getManager ().clearOverridesAdded ());
132+
133+ if (archiveCategoryOptional .isEmpty ()) {
134+ logger .warn ("Could not find archive category. Attempting to create one..." );
135+ channel .getGuild ()
136+ .createCategory (ARCHIVE_CATEGORY_NAME )
137+ .queue (newCategory -> restActionChain .and (channelManager .setParent (newCategory ))
138+ .queue ());
91139 return ;
92140 }
93141
94- channel .delete ()
95- .queue (_ -> logger .trace ("Deleted dynamically created voice channel: {} " ,
96- channel .getName ()),
97- error -> logger .error ("Failed to delete dynamically created voice channel: {} " ,
98- channel .getName (), error ));
142+ archiveCategoryOptional .ifPresent (archiveCategory -> restActionChain
143+ .and (channelManager .setParent (archiveCategory ))
144+ .queue (_ -> voiceChatCleanupStrategy .cleanup (archiveCategory .getVoiceChannels ()),
145+ err -> logger .error ("Could not archive dynamic voice chat" , err )));
99146 }
100147
101148 private void sendWarningEmbed (VoiceChannel channel ) {
0 commit comments