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

Conversation

SriRamanujam
Copy link

@SriRamanujam SriRamanujam commented Nov 4, 2021

This PR enables the bot to leave a voice channel after some time spent idle. Upon joining a channel, a scheduled task will fire every minute that checks to see if the player is idle. If it is, the task will increment an internal timer. After some configurable amount of time (idleTimeMinutes in ukulele.yml, defaults to 10 minutes) the task will have the bot automatically leave the voice channel.

First time working with Kotlin and Spring, so please do point out any stylistic foibles. If there's a linter I should run, I can do that no problem.

Fixes #26

Copy link
Owner

@freyacodes freyacodes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've changed the bot so that it will leave once the queue is empty after approximately 10 minutes without explanation

This is probably not what all Ukulele users want. This feature should default to be disabled.

@@ -9,5 +9,6 @@ class BotProps(
var prefix: String = "::",
var database: String = "./database",
var game: String = "",
var trackDurationLimit: Int = 0
var trackDurationLimit: Int = 0,
var idleTimeMinutes: Int = 10
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be disabled by default

Suggested change
var idleTimeMinutes: Int = 10
var idleTimeMinutes: Int = 0

@SriRamanujam
Copy link
Author

Okay, so in the process of reworking the PR with code review feedback I discovered a race condition of sorts. Basically, oftentimes the onVoiceJoin handler of the service would fire before the onTrackStart handler in Player, so the logic would determine see an empty queue and no playing track, which meant remainingDuration was 0, and so it would start the idle timeout task. If the video was under one minute long, that meant that the bot would leave early. Not the end of the world, but incorrect behavior nonetheless.

I tried a couple things to resolve the issue. I first tried implementing onTrackStart and onTrackEnd on LeaveOnIdleService itself, which didn't work for the same reason (couldn't guarantee the new handlers would be called after Player's handlers). The approach that does seem to work is what I've reworked this PR into: extending the event handlers on Player to call into LeaveOnIdleService. This neatly resolves the ordering issues, and so far has worked for me in my testing.

class Player(
private val beans: Beans,
guildProperties: GuildProperties,
private val leaveOnIdleService: LeaveOnIdleService,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're actually supposed to put this value in the Beans class and not change PlayerRegistry at all

* Create a new timer which will start counting from the current moment and cancel any existing timers
* associated with this guild.
*/
fun maybeCreateTimer(guild: Guild) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more appropriate name is onQueueEmpty. The player doesn't control this service

/**
* If a timer exists for this guild, cancel it.
*/
fun maybeDestroyTimer(guild: Guild) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destroyTimer sounds better. We don't need to communicate that there might not be a timer to destroy

// don't do anything if idleTimeMinutes is not set
if (botProps.idleTimeMinutes <= 0) return

timers[guild.idLong]?.let {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do timers.remove(guild.idLong)?.let { ... } and delete the other remove call

* a player in a guild has spent idling.
*/
class IdleTask(val guild: Guild, private val idleTimeMinutes: Int) : Runnable {
private var numberOfMinutesIdleElapsed = -1
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit odd to me that you're using a counter and not a unix timestamp, but I suppose this works too.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, could you not just schedule the voice leave after idleTimeMinutes minutes?

* Runnable that gets passed to the Spring TaskScheduler. Used to keep track of how many minutes
* a player in a guild has spent idling.
*/
class IdleTask(val guild: Guild, private val idleTimeMinutes: Int) : Runnable {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you make this an inner class you don't need to pass idleTimeMinutes

@SriRamanujam
Copy link
Author

Hi, anything else I can do to move this along? I believed I've addressed all the comments from the last round of review.

@Abi45x
Copy link

Abi45x commented Dec 26, 2021

Hi SriRamanujam, Freya is currently focusing her time on her bachelor project until the 4th of January, I would probably expect replies to be sparse until then

Copy link
Owner

@freyacodes freyacodes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the wait! There's one small thing I want you to change still

Unfortunately, trying to model the task as a duration-based timeout saw
race conditions where the callback to determine whether to start a timer
was called before the callback to add a new track to the player had run.
This caused an issue where the code believed that no track was playing
when in fact a track would be queued mere milliseconds later. When the
video in question was under a minute long, this led to a state where the
bot would leave the channel a minute early.

I've re-implemented the functionality such that it is invoked as part of
the onTrackStart and onTrackEnd callbacks of Player. This guarantees
that the idle timer kickoff logic runs _after_ all other Player state
updates have completed, which neatly avoids race/ordering bugs.
* put leaveOnIdleService in Player.Beans
* better names for leaveOnIdleService's callbacks
* Directly call timers.remove()
* Schedule VC leave task directly, rather than periodically calling
  timer
* Clean up redundant comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature request: Leave channel after some amount of time idle
3 participants