Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically leave voice channel after some time spent idle #42

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) {
Expand Down
25 changes: 21 additions & 4 deletions src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,29 @@ 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
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
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -9,6 +10,6 @@ class PlayerRegistry(val playerBeans: Player.Beans) {

private val players = mutableMapOf<Long, Player>()

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) }

}
5 changes: 3 additions & 2 deletions src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ class BotProps(
var database: String = "./database",
var game: String = "",
var trackDurationLimit: Int = 0,
var announceTracks: Boolean = false
)
var announceTracks: Boolean = false,
var idleTimeMinutes: Int = 0
)
46 changes: 46 additions & 0 deletions src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt
Original file line number Diff line number Diff line change
@@ -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<Long, ScheduledFuture<*>> = ConcurrentHashMap()

/**
* Create a new timer which will start counting from the current moment and cancel any existing timers
* associated with this guild.
freyacodes marked this conversation as resolved.
Show resolved Hide resolved
*/
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)
}
}
}
14 changes: 12 additions & 2 deletions src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
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
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)

Expand All @@ -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)
}
}