diff --git a/src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt b/src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt index eed3494..60a206b 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt @@ -3,9 +3,11 @@ package dev.arbjerg.ukulele import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.boot.runApplication +import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication @ConfigurationPropertiesScan +@EnableScheduling class UkuleleApplication fun main(args: Array) { diff --git a/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt b/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt index 9a48545..a188b30 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt @@ -11,8 +11,10 @@ import dev.arbjerg.ukulele.command.NowPlayingCommand import dev.arbjerg.ukulele.config.BotProps import dev.arbjerg.ukulele.data.GuildProperties import dev.arbjerg.ukulele.data.GuildPropertiesService +import dev.arbjerg.ukulele.features.LeaveOnIdleService import net.dv8tion.jda.api.audio.AudioSendHandler import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.entities.Guild import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -20,13 +22,18 @@ import java.nio.Buffer import java.nio.ByteBuffer -class Player(val beans: Beans, guildProperties: GuildProperties) : AudioEventAdapter(), AudioSendHandler { +class Player( + private val beans: Beans, + guildProperties: GuildProperties, + private val guild: Guild +) : AudioEventAdapter(), AudioSendHandler { @Component class Beans( val apm: AudioPlayerManager, val guildProperties: GuildPropertiesService, val nowPlayingCommand: NowPlayingCommand, - val botProps: BotProps + val botProps: BotProps, + val leaveOnIdleService: LeaveOnIdleService ) private val guildId = guildProperties.guildId @@ -127,14 +134,24 @@ class Player(val beans: Beans, guildProperties: GuildProperties) : AudioEventAda if (beans.botProps.announceTracks) { lastChannel?.sendMessage(beans.nowPlayingCommand.buildEmbed(track))?.queue() } + + log.debug("onTrackStart called for player in guild {}", guild.idLong) + beans.leaveOnIdleService.destroyTimer(guild) } override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) { + log.debug("onTrackEnd called for player in guild {}", guild.idLong) if (isRepeating && endReason.mayStartNext) { queue.add(track.makeClone()) } - val new = queue.take() ?: return - player.playTrack(new) + val new = queue.take() + if (new != null) { + player.playTrack(new) + } + + if (remainingDuration <= 0) { + beans.leaveOnIdleService.onQueueEmpty(guild) + } } override fun onTrackException(player: AudioPlayer, track: AudioTrack, exception: FriendlyException) { diff --git a/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt b/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt index e89b061..5dfcd66 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt @@ -1,6 +1,7 @@ package dev.arbjerg.ukulele.audio import dev.arbjerg.ukulele.data.GuildProperties +import dev.arbjerg.ukulele.features.LeaveOnIdleService import net.dv8tion.jda.api.entities.Guild import org.springframework.stereotype.Service @@ -9,6 +10,6 @@ class PlayerRegistry(val playerBeans: Player.Beans) { private val players = mutableMapOf() - fun get(guild: Guild, guildProperties: GuildProperties) = players.computeIfAbsent(guild.idLong) { Player(playerBeans, guildProperties) } + fun get(guild: Guild, guildProperties: GuildProperties) = players.computeIfAbsent(guild.idLong) { Player(playerBeans, guildProperties, guild) } } \ No newline at end of file diff --git a/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt b/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt index 61a66ed..b9d629a 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt @@ -10,5 +10,6 @@ class BotProps( var database: String = "./database", var game: String = "", var trackDurationLimit: Int = 0, - var announceTracks: Boolean = false -) \ No newline at end of file + var announceTracks: Boolean = false, + var idleTimeMinutes: Int = 0 +) diff --git a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt new file mode 100644 index 0000000..3443709 --- /dev/null +++ b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt @@ -0,0 +1,46 @@ +package dev.arbjerg.ukulele.features + +import org.springframework.stereotype.Service +import dev.arbjerg.ukulele.config.BotProps +import net.dv8tion.jda.api.entities.Guild +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.scheduling.TaskScheduler +import java.time.Instant +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ScheduledFuture + +@Service +class LeaveOnIdleService(private val scheduler: TaskScheduler, private val botProps: BotProps) { + private val log: Logger = LoggerFactory.getLogger(LeaveOnIdleService::class.java) + private val timers: ConcurrentHashMap> = ConcurrentHashMap() + + /** + * Create a new timer which will start counting from the current moment and cancel any existing timers + * associated with this guild. + */ + fun onQueueEmpty(guild: Guild) { + if (botProps.idleTimeMinutes <= 0) return + + log.info("Player for guild {} is idle, starting timeout of {} minutes", guild.idLong, botProps.idleTimeMinutes) + val instant = Instant.now().plusSeconds(60 * botProps.idleTimeMinutes.toLong()) + val fut = scheduler.schedule({ + guild.audioManager.closeAudioConnection() + }, instant) + + if (timers.containsKey(guild.idLong)) { + timers.replace(guild.idLong, fut)?.cancel(true) + } else { + timers[guild.idLong] = fut + } + } + + fun destroyTimer(guild: Guild) { + if (botProps.idleTimeMinutes <= 0) return + + timers.remove(guild.idLong)?.let { + log.debug("Cleaned up idle timer for guild {}", guild.idLong) + it.cancel(true) + } + } +} diff --git a/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt b/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt index fd9705c..4556969 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt @@ -1,7 +1,9 @@ package dev.arbjerg.ukulele.jda -import net.dv8tion.jda.api.events.ReadyEvent +import dev.arbjerg.ukulele.features.LeaveOnIdleService import net.dv8tion.jda.api.events.StatusChangeEvent +import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent +import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent import net.dv8tion.jda.api.hooks.ListenerAdapter import org.slf4j.Logger @@ -9,7 +11,7 @@ import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @Service -class EventHandler(private val commandManager: CommandManager) : ListenerAdapter() { +class EventHandler(private val commandManager: CommandManager, private val leaveOnIdleService: LeaveOnIdleService) : ListenerAdapter() { private val log: Logger = LoggerFactory.getLogger(EventHandler::class.java) @@ -22,4 +24,12 @@ class EventHandler(private val commandManager: CommandManager) : ListenerAdapter log.info("{}: {} -> {}", event.entity.shardInfo, event.oldStatus, event.newStatus) } + override fun onGuildVoiceJoin(event: GuildVoiceJoinEvent) { + log.info("Joining voice channel {} in guild {}", event.channelJoined, event.guild) + } + + override fun onGuildVoiceLeave(event: GuildVoiceLeaveEvent) { + log.info("Leaving voice channel {} in guild {}", event.channelLeft, event.guild) + leaveOnIdleService.destroyTimer(event.guild) + } } \ No newline at end of file