From db86c133e60488b943dce183b96be4e8c43f796a Mon Sep 17 00:00:00 2001 From: anthorob Date: Sat, 3 Oct 2026 18:21:10 -0400 Subject: [PATCH] Add new endpoints for queue management and playback control Introduced `/music/{guildId}/queue` DELETE, `/music/{guildId}/leave`, and `/music/{guildId}/shuffle` endpoints for enhanced queue and playback operations. Added corresponding DTOs (`DeleteQueueResponse`, `LeaveResponse`, `ShuffleResponse`) and updated `MusicRoutes` with OpenAPI documentation. These changes improve queue handling, bot disconnection, and randomization functionality for guilds. --- .../http/server/MusicRoutes.java | 81 +++++++++++++++++++ .../http/server/dto/DeleteQueueResponse.java | 21 +++++ .../http/server/dto/LeaveResponse.java | 16 ++++ .../http/server/dto/ShuffleResponse.java | 21 +++++ 4 files changed, 139 insertions(+) create mode 100644 src/main/java/me/bobert/netneutrality/http/server/dto/DeleteQueueResponse.java create mode 100644 src/main/java/me/bobert/netneutrality/http/server/dto/LeaveResponse.java create mode 100644 src/main/java/me/bobert/netneutrality/http/server/dto/ShuffleResponse.java diff --git a/src/main/java/me/bobert/netneutrality/http/server/MusicRoutes.java b/src/main/java/me/bobert/netneutrality/http/server/MusicRoutes.java index 89d8d3e..2d930f4 100644 --- a/src/main/java/me/bobert/netneutrality/http/server/MusicRoutes.java +++ b/src/main/java/me/bobert/netneutrality/http/server/MusicRoutes.java @@ -18,8 +18,10 @@ import io.javalin.openapi.OpenApiParam; import io.javalin.openapi.OpenApiRequestBody; import io.javalin.openapi.OpenApiResponse; import me.bobert.netneutrality.http.server.dto.CommandResultResponse; +import me.bobert.netneutrality.http.server.dto.DeleteQueueResponse; import me.bobert.netneutrality.http.server.dto.DjStatusResponse; import me.bobert.netneutrality.http.server.dto.ErrorResponse; +import me.bobert.netneutrality.http.server.dto.LeaveResponse; import me.bobert.netneutrality.http.server.dto.MusicSourceResponse; import me.bobert.netneutrality.http.server.dto.MusicStatusResponse; import me.bobert.netneutrality.http.server.dto.QueueRequest; @@ -30,6 +32,7 @@ import me.bobert.netneutrality.http.server.dto.ReorderResponse; import me.bobert.netneutrality.http.server.dto.SearchRequest; import me.bobert.netneutrality.http.server.dto.SeekRequest; import me.bobert.netneutrality.http.server.dto.SeekResponse; +import me.bobert.netneutrality.http.server.dto.ShuffleResponse; import me.bobert.netneutrality.http.server.dto.VolumeRequest; import me.bobert.netneutrality.http.server.dto.VolumeResponse; import me.bobert.netneutrality.music.MusicManager; @@ -78,8 +81,11 @@ public final class MusicRoutes { routes.post("/music/{guildId}/vote-skip/{userId}", ctx -> voteSkip(jdaSupplier, musicService, ctx)); routes.get("/music/{guildId}/dj/{userId}", ctx -> dj(jdaSupplier, musicService, ctx)); routes.post("/music/{guildId}/queue", ctx -> play(jdaSupplier, musicService, ctx)); + routes.delete("/music/{guildId}/queue", ctx -> deleteQueue(jdaSupplier, musicService, ctx)); routes.patch("/music/{guildId}/queue/reorder", ctx -> reorder(jdaSupplier, musicService, ctx)); routes.post("/music/{guildId}/search", ctx -> search(jdaSupplier, musicService, ctx)); + routes.post("/music/{guildId}/leave", ctx -> leave(jdaSupplier, musicService, ctx)); + routes.post("/music/{guildId}/shuffle", ctx -> shuffle(jdaSupplier, musicService, ctx)); } @OpenApi( @@ -496,6 +502,81 @@ public final class MusicRoutes { })); } + @OpenApi( + path = "/music/{guildId}/queue", + methods = HttpMethod.DELETE, + summary = "Clear the queue, or remove a single track from it when a position is provided", + tags = {"Music"}, + pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"), + queryParams = @OpenApiParam(name = "position", description = "1-based position of the track to remove (position 1 is the currently playing track); omit to clear the whole queue", required = false), + responses = { + @OpenApiResponse(status = "200", content = @OpenApiContent(from = DeleteQueueResponse.class)), + @OpenApiResponse(status = "400", content = @OpenApiContent(from = ErrorResponse.class)), + @OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class)) + } + ) + private static void deleteQueue(Supplier jdaSupplier, MusicService musicService, Context ctx) { + MusicManager manager = manager(jdaSupplier, musicService, ctx); + String position = ctx.queryParam("position"); + + QueueActionResult result; + if (position == null || position.isBlank()) { + result = manager.getQueueManager().clearQueue(); + } else { + int index; + try { + index = Integer.parseInt(position); + } catch (NumberFormatException e) { + throw new BadRequestResponse("position must be an integer"); + } + + result = manager.getQueueManager().removeFromQueue(index); + } + + if (result.getStatus() == QueueActionResult.Status.ERROR) { + throw new BadRequestResponse(result.getMessage()); + } + + announce(manager, result.getMessage()); + ctx.json(resultBodyWithQueue(result, manager)); + } + + @OpenApi( + path = "/music/{guildId}/leave", + methods = HttpMethod.POST, + summary = "Disconnect the bot from its current voice channel in a guild, stopping playback", + tags = {"Music"}, + pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"), + responses = { + @OpenApiResponse(status = "200", content = @OpenApiContent(from = LeaveResponse.class)), + @OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class)) + } + ) + private static void leave(Supplier jdaSupplier, MusicService musicService, Context ctx) { + MusicManager manager = manager(jdaSupplier, musicService, ctx); + manager.disconnect(); + announce(manager, "Leaving voice channel"); + ctx.json(new LeaveResponse("ok", "Leaving voice channel")); + } + + @OpenApi( + path = "/music/{guildId}/shuffle", + methods = HttpMethod.POST, + summary = "Shuffle the queue, keeping the currently playing track in place", + tags = {"Music"}, + pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"), + responses = { + @OpenApiResponse(status = "200", content = @OpenApiContent(from = ShuffleResponse.class)), + @OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class)) + } + ) + private static void shuffle(Supplier jdaSupplier, MusicService musicService, Context ctx) { + MusicManager manager = manager(jdaSupplier, musicService, ctx); + QueueActionResult result = manager.getQueueManager().shuffle(); + announce(manager, result.getMessage()); + ctx.json(resultBodyWithQueue(result, manager)); + } + @OpenApi( path = "/music/{guildId}/queue/reorder", methods = HttpMethod.PATCH, diff --git a/src/main/java/me/bobert/netneutrality/http/server/dto/DeleteQueueResponse.java b/src/main/java/me/bobert/netneutrality/http/server/dto/DeleteQueueResponse.java new file mode 100644 index 0000000..f04217b --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/http/server/dto/DeleteQueueResponse.java @@ -0,0 +1,21 @@ +package me.bobert.netneutrality.http.server.dto; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import java.util.List; + +/** + * OpenAPI schema for the {@code /music/{guildId}/queue} {@code DELETE} response, which extends + * the standard {@link CommandResultResponse} with the current queue listing so the caller can + * resync its state after clearing the queue or removing a single track from it. + */ +public record DeleteQueueResponse( + String status, + String message, + List queue +) { +} diff --git a/src/main/java/me/bobert/netneutrality/http/server/dto/LeaveResponse.java b/src/main/java/me/bobert/netneutrality/http/server/dto/LeaveResponse.java new file mode 100644 index 0000000..f2016ac --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/http/server/dto/LeaveResponse.java @@ -0,0 +1,16 @@ +package me.bobert.netneutrality.http.server.dto; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +/** + * OpenAPI schema for the {@code /music/{guildId}/leave} response. + */ +public record LeaveResponse( + String status, + String message +) { +} diff --git a/src/main/java/me/bobert/netneutrality/http/server/dto/ShuffleResponse.java b/src/main/java/me/bobert/netneutrality/http/server/dto/ShuffleResponse.java new file mode 100644 index 0000000..da93aeb --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/http/server/dto/ShuffleResponse.java @@ -0,0 +1,21 @@ +package me.bobert.netneutrality.http.server.dto; + +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import java.util.List; + +/** + * OpenAPI schema for the {@code /music/{guildId}/shuffle} response, which extends the standard + * {@link CommandResultResponse} with the current queue listing so the caller can resync its + * state after a shuffle. + */ +public record ShuffleResponse( + String status, + String message, + List queue +) { +}