22
33import net .dv8tion .jda .api .JDA ;
44import net .dv8tion .jda .api .entities .channel .Channel ;
5+ import net .dv8tion .jda .api .entities .channel .unions .AudioChannelUnion ;
6+ import net .dv8tion .jda .api .events .guild .voice .GuildVoiceDeafenEvent ;
7+ import net .dv8tion .jda .api .events .guild .voice .GuildVoiceMuteEvent ;
8+ import net .dv8tion .jda .api .events .guild .voice .GuildVoiceStreamEvent ;
9+ import net .dv8tion .jda .api .events .guild .voice .GuildVoiceUpdateEvent ;
10+ import net .dv8tion .jda .api .events .guild .voice .GuildVoiceVideoEvent ;
511import net .dv8tion .jda .api .events .interaction .ModalInteractionEvent ;
612import net .dv8tion .jda .api .events .interaction .command .CommandAutoCompleteInteractionEvent ;
713import net .dv8tion .jda .api .events .interaction .command .MessageContextInteractionEvent ;
1622import net .dv8tion .jda .api .hooks .ListenerAdapter ;
1723import net .dv8tion .jda .api .interactions .callbacks .IReplyCallback ;
1824import net .dv8tion .jda .api .interactions .components .ComponentInteraction ;
25+ import org .jetbrains .annotations .NotNull ;
26+ import org .jetbrains .annotations .Nullable ;
1927import org .jetbrains .annotations .Unmodifiable ;
2028import org .slf4j .Logger ;
2129import org .slf4j .LoggerFactory ;
3240import org .togetherjava .tjbot .features .UserContextCommand ;
3341import org .togetherjava .tjbot .features .UserInteractionType ;
3442import org .togetherjava .tjbot .features .UserInteractor ;
43+ import org .togetherjava .tjbot .features .VoiceReceiver ;
3544import org .togetherjava .tjbot .features .componentids .ComponentId ;
3645import org .togetherjava .tjbot .features .componentids .ComponentIdParser ;
3746import org .togetherjava .tjbot .features .componentids .ComponentIdStore ;
@@ -75,6 +84,7 @@ public final class BotCore extends ListenerAdapter implements CommandProvider {
7584 private final ComponentIdParser componentIdParser ;
7685 private final ComponentIdStore componentIdStore ;
7786 private final Map <Pattern , MessageReceiver > channelNameToMessageReceiver = new HashMap <>();
87+ private final Map <Pattern , VoiceReceiver > channelNameToVoiceReceiver = new HashMap <>();
7888
7989 /**
8090 * Creates a new command system which uses the given database to allow commands to persist data.
@@ -96,6 +106,13 @@ public BotCore(JDA jda, Database database, Config config) {
96106 .forEach (messageReceiver -> channelNameToMessageReceiver
97107 .put (messageReceiver .getChannelNamePattern (), messageReceiver ));
98108
109+ // Voice receivers
110+ features .stream ()
111+ .filter (VoiceReceiver .class ::isInstance )
112+ .map (VoiceReceiver .class ::cast )
113+ .forEach (voiceReceiver -> channelNameToVoiceReceiver
114+ .put (voiceReceiver .getChannelNamePattern (), voiceReceiver ));
115+
99116 // Event receivers
100117 features .stream ()
101118 .filter (EventReceiver .class ::isInstance )
@@ -238,6 +255,96 @@ public void onMessageDelete(final MessageDeleteEvent event) {
238255 }
239256 }
240257
258+ /**
259+ * Calculates the correct voice channel to act upon.
260+ *
261+ * <p>
262+ * If there is a <code>channelJoined</code> and a <code>channelLeft</code>, then the
263+ * <code>channelJoined</code> is prioritized and returned. Otherwise, it returns
264+ * <code>channelLeft</code>.
265+ *
266+ * <p>
267+ * This is an essential method due to the need of updating both channel categories that a member
268+ * utilizes. For example, take the scenario of a user browsing through voice channels:
269+ *
270+ * <pre>
271+ * - User joins General -> channelJoined = General | channelLeft = null
272+ * - User switches to Gaming -> channelJoined = Gaming | channelLeft = General
273+ * - User leaves Discord -> channelJoined = null | channelLeft = Gaming
274+ * </pre>
275+ *
276+ * <p>
277+ * This way, we make sure that all relevant voice channels are updated.
278+ *
279+ * @param channelJoined the channel that the member has connected to, if any
280+ * @param channelLeft the channel that the member left from, if any
281+ * @return the join channel if not null, otherwise the leave channel, otherwise an empty
282+ * optional
283+ */
284+ private Optional <Channel > selectPreferredAudioChannel (@ Nullable AudioChannelUnion channelJoined ,
285+ @ Nullable AudioChannelUnion channelLeft ) {
286+ if (channelJoined != null ) {
287+ return Optional .of (channelJoined );
288+ }
289+
290+ return Optional .ofNullable (channelLeft );
291+ }
292+
293+ @ Override
294+ public void onGuildVoiceUpdate (@ NotNull GuildVoiceUpdateEvent event ) {
295+ selectPreferredAudioChannel (event .getChannelJoined (), event .getChannelLeft ())
296+ .ifPresent (channel -> getVoiceReceiversSubscribedTo (channel )
297+ .forEach (voiceReceiver -> voiceReceiver .onVoiceUpdate (event )));
298+ }
299+
300+ @ Override
301+ public void onGuildVoiceVideo (@ NotNull GuildVoiceVideoEvent event ) {
302+ AudioChannelUnion channel = event .getVoiceState ().getChannel ();
303+
304+ if (channel == null ) {
305+ return ;
306+ }
307+
308+ getVoiceReceiversSubscribedTo (channel )
309+ .forEach (voiceReceiver -> voiceReceiver .onVideoToggle (event ));
310+ }
311+
312+ @ Override
313+ public void onGuildVoiceStream (@ NotNull GuildVoiceStreamEvent event ) {
314+ AudioChannelUnion channel = event .getVoiceState ().getChannel ();
315+
316+ if (channel == null ) {
317+ return ;
318+ }
319+
320+ getVoiceReceiversSubscribedTo (channel )
321+ .forEach (voiceReceiver -> voiceReceiver .onStreamToggle (event ));
322+ }
323+
324+ @ Override
325+ public void onGuildVoiceMute (@ NotNull GuildVoiceMuteEvent event ) {
326+ AudioChannelUnion channel = event .getVoiceState ().getChannel ();
327+
328+ if (channel == null ) {
329+ return ;
330+ }
331+
332+ getVoiceReceiversSubscribedTo (channel )
333+ .forEach (voiceReceiver -> voiceReceiver .onMuteToggle (event ));
334+ }
335+
336+ @ Override
337+ public void onGuildVoiceDeafen (@ NotNull GuildVoiceDeafenEvent event ) {
338+ AudioChannelUnion channel = event .getVoiceState ().getChannel ();
339+
340+ if (channel == null ) {
341+ return ;
342+ }
343+
344+ getVoiceReceiversSubscribedTo (channel )
345+ .forEach (voiceReceiver -> voiceReceiver .onDeafenToggle (event ));
346+ }
347+
241348 private Stream <MessageReceiver > getMessageReceiversSubscribedTo (Channel channel ) {
242349 String channelName = channel .getName ();
243350 return channelNameToMessageReceiver .entrySet ()
@@ -248,6 +355,16 @@ private Stream<MessageReceiver> getMessageReceiversSubscribedTo(Channel channel)
248355 .map (Map .Entry ::getValue );
249356 }
250357
358+ private Stream <VoiceReceiver > getVoiceReceiversSubscribedTo (Channel channel ) {
359+ String channelName = channel .getName ();
360+ return channelNameToVoiceReceiver .entrySet ()
361+ .stream ()
362+ .filter (patternAndReceiver -> patternAndReceiver .getKey ()
363+ .matcher (channelName )
364+ .matches ())
365+ .map (Map .Entry ::getValue );
366+ }
367+
251368 @ Override
252369 public void onSlashCommandInteraction (SlashCommandInteractionEvent event ) {
253370 String name = event .getName ();
0 commit comments