Add music API endpoints and update OpenAPI responses
Introduced `/music/*` routes for managing music player actions via HTTP, including status, queue, skip, stop, volume control, and vote-skip functionality. Refactored settings and guild routes to use new `Response` DTOs for precise OpenAPI schema definitions. Updated `WebApi` to register music routes and ensure proper integration with the existing `MusicService` logic.
This commit is contained in:
1 parent
e9c6f403e5
commit
0f96051fd0
34 files changed
+762
-35
No files matched your search
@@ -161,7 +161,7 @@ public class App {
|
||||
}, () -> { }));
|
||||
|
||||
// Web API (priority 70) — Lifecycle with graceful stop.
|
||||
context.register(new WebApi(settings, guildServices, this::getJDA, this::shutdown));
|
||||
context.register(new WebApi(settings, guildServices, music, this::getJDA, this::shutdown));
|
||||
}
|
||||
|
||||
private static Lifecycle lifecycle(int priority, ThrowingRunnable start, Runnable stop) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.javalin.openapi.HttpMethod;
|
||||
import io.javalin.openapi.OpenApi;
|
||||
import io.javalin.openapi.OpenApiContent;
|
||||
import io.javalin.openapi.OpenApiResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.StatusResponse;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -47,7 +48,7 @@ public final class ActionRoutes {
|
||||
methods = HttpMethod.GET,
|
||||
summary = "Report the current Discord gateway health",
|
||||
tags = {"Actions"},
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class))
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = StatusResponse.class))
|
||||
)
|
||||
private static void status(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
JDA jda = GuildRoutes.jda(jdaSupplier);
|
||||
|
||||
@@ -14,6 +14,14 @@ import io.javalin.openapi.HttpMethod;
|
||||
import io.javalin.openapi.OpenApi;
|
||||
import io.javalin.openapi.OpenApiContent;
|
||||
import io.javalin.openapi.OpenApiResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.ChannelSummaryResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.ErrorResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.GuildOwnerEntryResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.GuildSummaryResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.MemberRolesResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.MemberSummaryResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.OwnerResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.VoiceChannelSummaryResponse;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
@@ -50,7 +58,7 @@ public final class GuildRoutes {
|
||||
methods = HttpMethod.GET,
|
||||
summary = "List every guild the bot is currently in",
|
||||
tags = {"Guilds"},
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = Map[].class))
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = GuildSummaryResponse[].class))
|
||||
)
|
||||
private static void guilds(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
ctx.json(
|
||||
@@ -90,7 +98,7 @@ public final class GuildRoutes {
|
||||
methods = HttpMethod.GET,
|
||||
summary = "List every bot guild with its owner id",
|
||||
tags = {"Guilds"},
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = Map[].class))
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = GuildOwnerEntryResponse[].class))
|
||||
)
|
||||
private static void owner(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
ctx.json(
|
||||
@@ -107,8 +115,8 @@ public final class GuildRoutes {
|
||||
tags = {"Guilds"},
|
||||
pathParams = @io.javalin.openapi.OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = OwnerResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void ownerOf(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
@@ -129,8 +137,8 @@ public final class GuildRoutes {
|
||||
tags = {"Guilds"},
|
||||
pathParams = @io.javalin.openapi.OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map[].class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = ChannelSummaryResponse[].class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void channels(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
@@ -148,8 +156,8 @@ public final class GuildRoutes {
|
||||
tags = {"Guilds"},
|
||||
pathParams = @io.javalin.openapi.OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map[].class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = ChannelSummaryResponse[].class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void roles(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
@@ -170,8 +178,8 @@ public final class GuildRoutes {
|
||||
@io.javalin.openapi.OpenApiParam(name = "userId", description = "The Discord user id")
|
||||
},
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = MemberSummaryResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void member(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
@@ -196,8 +204,8 @@ public final class GuildRoutes {
|
||||
@io.javalin.openapi.OpenApiParam(name = "userId", description = "The Discord user id")
|
||||
},
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = MemberRolesResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void memberRoles(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
@@ -245,8 +253,8 @@ public final class GuildRoutes {
|
||||
tags = {"Guilds"},
|
||||
pathParams = @io.javalin.openapi.OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map[].class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = VoiceChannelSummaryResponse[].class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void voiceChannels(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
@@ -267,8 +275,8 @@ public final class GuildRoutes {
|
||||
@io.javalin.openapi.OpenApiParam(name = "channelId", description = "The Discord voice channel id")
|
||||
},
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = VoiceChannelSummaryResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void voiceChannel(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
package me.bobert.netneutrality.http.server;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import dev.arbjerg.lavalink.client.player.Track;
|
||||
import io.javalin.config.RoutesConfig;
|
||||
import io.javalin.http.BadRequestResponse;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.openapi.HttpMethod;
|
||||
import io.javalin.openapi.OpenApi;
|
||||
import io.javalin.openapi.OpenApiContent;
|
||||
import io.javalin.openapi.OpenApiParam;
|
||||
import io.javalin.openapi.OpenApiRequestBody;
|
||||
import io.javalin.openapi.OpenApiResponse;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.http.server.dto.CommandResultResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.ErrorResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.MusicStatusResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.QueuedTrackResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.VolumeRequest;
|
||||
import me.bobert.netneutrality.http.server.dto.VolumeResponse;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.music.TrackInfo;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Registers the {@code /music/*} endpoints on a {@link RoutesConfig} instance.
|
||||
*
|
||||
* <p>These expose the guild music player over HTTP, mirroring the slash commands. Vote-skipping
|
||||
* in particular has no Lavalink API equivalent, so it is only reachable through
|
||||
* {@link MusicManager#voteSkip(Member)}.
|
||||
*/
|
||||
public final class MusicRoutes {
|
||||
|
||||
private MusicRoutes() {
|
||||
}
|
||||
|
||||
public static void register(RoutesConfig routes, Supplier<JDA> jdaSupplier, MusicService musicService) {
|
||||
routes.get("/music/{guildId}/status", ctx -> status(jdaSupplier, musicService, ctx));
|
||||
routes.get("/music/{guildId}/queue", ctx -> queue(jdaSupplier, musicService, ctx));
|
||||
routes.post("/music/{guildId}/skip", ctx -> skip(jdaSupplier, musicService, ctx));
|
||||
routes.post("/music/{guildId}/stop", ctx -> stop(jdaSupplier, musicService, ctx));
|
||||
routes.put("/music/{guildId}/volume", ctx -> volume(jdaSupplier, musicService, ctx));
|
||||
routes.post("/music/{guildId}/vote-skip/{userId}", ctx -> voteSkip(jdaSupplier, musicService, ctx));
|
||||
}
|
||||
|
||||
@OpenApi(
|
||||
path = "/music/{guildId}/status",
|
||||
methods = HttpMethod.GET,
|
||||
summary = "Get the current player status of a guild",
|
||||
tags = {"Music"},
|
||||
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = MusicStatusResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void status(Supplier<JDA> jdaSupplier, MusicService musicService, Context ctx) {
|
||||
MusicManager manager = manager(jdaSupplier, musicService, ctx);
|
||||
AudioChannel channel = manager.getConnectedChannel();
|
||||
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("connected", manager.isConnected());
|
||||
body.put("channel_id", channel == null ? null : channel.getId());
|
||||
body.put("channel_name", channel == null ? null : channel.getName());
|
||||
body.put("playing", manager.isCurrentlyPlaying());
|
||||
body.put("current_track", trackSummary(manager.getCurrentlyPlaying()));
|
||||
body.put("position_ms", manager.getCurrentPosition());
|
||||
body.put("queue_size", manager.getQueueSize());
|
||||
|
||||
ctx.json(body);
|
||||
}
|
||||
|
||||
@OpenApi(
|
||||
path = "/music/{guildId}/queue",
|
||||
methods = HttpMethod.GET,
|
||||
summary = "List the queued tracks of a guild",
|
||||
tags = {"Music"},
|
||||
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = QueuedTrackResponse[].class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void queue(Supplier<JDA> jdaSupplier, MusicService musicService, Context ctx) {
|
||||
MusicManager manager = manager(jdaSupplier, musicService, ctx);
|
||||
|
||||
ctx.json(
|
||||
manager.getQueueManager().getQueue().stream()
|
||||
.map(MusicRoutes::trackInfoSummary)
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
@OpenApi(
|
||||
path = "/music/{guildId}/skip",
|
||||
methods = HttpMethod.POST,
|
||||
summary = "Force-skip the currently playing track",
|
||||
tags = {"Music"},
|
||||
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = CommandResultResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void skip(Supplier<JDA> jdaSupplier, MusicService musicService, Context ctx) {
|
||||
MusicManager manager = manager(jdaSupplier, musicService, ctx);
|
||||
ctx.json(resultBody(manager.getQueueManager().skip()));
|
||||
}
|
||||
|
||||
@OpenApi(
|
||||
path = "/music/{guildId}/stop",
|
||||
methods = HttpMethod.POST,
|
||||
summary = "Stop the currently playing track, keeping the queue",
|
||||
tags = {"Music"},
|
||||
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = CommandResultResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void stop(Supplier<JDA> jdaSupplier, MusicService musicService, Context ctx) {
|
||||
MusicManager manager = manager(jdaSupplier, musicService, ctx);
|
||||
manager.stopTrack();
|
||||
ctx.json(Map.of("status", "ok", "message", "Stopping current track"));
|
||||
}
|
||||
|
||||
@OpenApi(
|
||||
path = "/music/{guildId}/volume",
|
||||
methods = HttpMethod.PUT,
|
||||
summary = "Set the player volume of a guild",
|
||||
tags = {"Music"},
|
||||
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
requestBody = @OpenApiRequestBody(content = @OpenApiContent(from = VolumeRequest.class)),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = VolumeResponse.class)),
|
||||
@OpenApiResponse(status = "400", content = @OpenApiContent(from = ErrorResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void volume(Supplier<JDA> jdaSupplier, MusicService musicService, Context ctx) {
|
||||
MusicManager manager = manager(jdaSupplier, musicService, ctx);
|
||||
int newVolume = volume(ctx);
|
||||
|
||||
manager.setVolume(newVolume);
|
||||
ctx.json(Map.of("status", "ok", "volume", newVolume));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and validates the {@code volume} field of the request body, failing with 400 when
|
||||
* missing or out of the {@code [0, 100]} range.
|
||||
*/
|
||||
private static int volume(Context ctx) {
|
||||
Object rawVolume = ctx.bodyAsClass(Map.class).get("volume");
|
||||
int volume;
|
||||
try {
|
||||
volume = rawVolume instanceof Number number ? number.intValue() : Integer.parseInt(String.valueOf(rawVolume));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadRequestResponse("volume must be an integer between 0 and 100");
|
||||
}
|
||||
|
||||
if (volume < 0 || volume > 100) {
|
||||
throw new BadRequestResponse("volume must be an integer between 0 and 100");
|
||||
}
|
||||
|
||||
return volume;
|
||||
}
|
||||
|
||||
@OpenApi(
|
||||
path = "/music/{guildId}/vote-skip/{userId}",
|
||||
methods = HttpMethod.POST,
|
||||
summary = "Cast a vote to skip the currently playing track on behalf of a guild member",
|
||||
tags = {"Music"},
|
||||
pathParams = {
|
||||
@OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
@OpenApiParam(name = "userId", description = "The Discord user id casting the vote")
|
||||
},
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = CommandResultResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void voteSkip(Supplier<JDA> jdaSupplier, MusicService musicService, Context ctx) {
|
||||
Guild guild = GuildRoutes.guild(jdaSupplier, ctx);
|
||||
MusicManager manager = musicService.get(guild);
|
||||
String userId = ctx.pathParam("userId");
|
||||
|
||||
CacheOrFetch.<Member>resolve(
|
||||
ctx,
|
||||
() -> guild.getMemberById(userId),
|
||||
() -> guild.retrieveMemberById(userId),
|
||||
member -> resultBody(manager.voteSkip(member))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link MusicManager} for the guild path parameter, creating it on first use.
|
||||
*/
|
||||
private static MusicManager manager(Supplier<JDA> jdaSupplier, MusicService musicService, Context ctx) {
|
||||
return musicService.get(GuildRoutes.guild(jdaSupplier, ctx));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a JSON-friendly summary for a {@link CommandExecutionResult}.
|
||||
*/
|
||||
private static Map<String, Object> resultBody(CommandExecutionResult result) {
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("status", result.getResult().name());
|
||||
body.put("message", result.getMessage());
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a JSON-friendly summary for the currently playing track, or {@code null} when idle.
|
||||
*/
|
||||
private static Map<String, Object> trackSummary(Track track) {
|
||||
if (track == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> summary = new LinkedHashMap<>();
|
||||
summary.put("title", track.getInfo().getTitle());
|
||||
summary.put("author", track.getInfo().getAuthor());
|
||||
summary.put("uri", track.getInfo().getUri());
|
||||
summary.put("duration_ms", track.getInfo().getLength());
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a JSON-friendly summary for a queued {@link TrackInfo}.
|
||||
*/
|
||||
private static Map<String, Object> trackInfoSummary(TrackInfo info) {
|
||||
Map<String, Object> summary = trackSummary(info.getTrack());
|
||||
summary.put("requester_id", info.getRequester().getId());
|
||||
summary.put("skip_count", info.getSkipCount());
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,12 @@ import me.bobert.netneutrality.db.GSettings;
|
||||
import me.bobert.netneutrality.db.SettingType;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.db.service.GuildSettingService;
|
||||
import me.bobert.netneutrality.http.server.dto.ErrorResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.SettingCatalogEntryResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.SettingsCurrentResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.SettingsReloadResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.SettingsUpdateRequest;
|
||||
import me.bobert.netneutrality.http.server.dto.SettingsUpdateResponse;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
@@ -50,7 +56,7 @@ public final class SettingsRoutes {
|
||||
methods = HttpMethod.GET,
|
||||
summary = "List every known per-guild setting with its default value, description, and type",
|
||||
tags = {"Settings"},
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = Map[].class))
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = SettingCatalogEntryResponse[].class))
|
||||
)
|
||||
private static void settingsCatalog(Context ctx) {
|
||||
ctx.json(
|
||||
@@ -77,8 +83,8 @@ public final class SettingsRoutes {
|
||||
tags = {"Settings"},
|
||||
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = SettingsCurrentResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void settingsCurrent(Supplier<JDA> jdaSupplier, GuildServiceProvider guildServices, Context ctx) {
|
||||
@@ -97,11 +103,11 @@ public final class SettingsRoutes {
|
||||
@OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
@OpenApiParam(name = "name", description = "The setting name, as returned by /settings/catalog")
|
||||
},
|
||||
requestBody = @OpenApiRequestBody(content = @OpenApiContent(from = Map.class)),
|
||||
requestBody = @OpenApiRequestBody(content = @OpenApiContent(from = SettingsUpdateRequest.class)),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "400", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = SettingsUpdateResponse.class)),
|
||||
@OpenApiResponse(status = "400", content = @OpenApiContent(from = ErrorResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void settingsUpdate(Supplier<JDA> jdaSupplier, GuildServiceProvider guildServices, Context ctx) {
|
||||
@@ -185,8 +191,8 @@ public final class SettingsRoutes {
|
||||
tags = {"Settings"},
|
||||
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = SettingsReloadResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void settingsReload(Supplier<JDA> jdaSupplier, GuildServiceProvider guildServices, Context ctx) {
|
||||
|
||||
@@ -15,6 +15,9 @@ import io.javalin.openapi.OpenApiParam;
|
||||
import io.javalin.openapi.OpenApiResponse;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.http.server.dto.BotStatsResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.ErrorResponse;
|
||||
import me.bobert.netneutrality.http.server.dto.GuildStatsResponse;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
@@ -39,7 +42,7 @@ public final class StatsRoutes {
|
||||
methods = HttpMethod.GET,
|
||||
summary = "Report bot-wide stats (guild count, gateway health, uptime)",
|
||||
tags = {"Stats"},
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class))
|
||||
responses = @OpenApiResponse(status = "200", content = @OpenApiContent(from = BotStatsResponse.class))
|
||||
)
|
||||
private static void bot(Supplier<JDA> jdaSupplier, Context ctx) {
|
||||
JDA jda = GuildRoutes.jda(jdaSupplier);
|
||||
@@ -59,8 +62,8 @@ public final class StatsRoutes {
|
||||
tags = {"Stats"},
|
||||
pathParams = @OpenApiParam(name = "guildId", description = "The Discord guild id"),
|
||||
responses = {
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = Map.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = Map.class))
|
||||
@OpenApiResponse(status = "200", content = @OpenApiContent(from = GuildStatsResponse.class)),
|
||||
@OpenApiResponse(status = "404", content = @OpenApiContent(from = ErrorResponse.class))
|
||||
}
|
||||
)
|
||||
private static void guild(Supplier<JDA> jdaSupplier, GuildServiceProvider guildServices, Context ctx) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.javalin.openapi.plugin.OpenApiPlugin;
|
||||
import io.javalin.openapi.plugin.swagger.SwaggerPlugin;
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -30,6 +31,7 @@ public class WebApi implements Lifecycle {
|
||||
|
||||
private final SettingsProvider settings;
|
||||
private final GuildServiceProvider guildServices;
|
||||
private final MusicService musicService;
|
||||
private final Supplier<JDA> jdaSupplier;
|
||||
private final Runnable shutdownHook;
|
||||
private Javalin app;
|
||||
@@ -37,11 +39,13 @@ public class WebApi implements Lifecycle {
|
||||
public WebApi(
|
||||
SettingsProvider settings,
|
||||
GuildServiceProvider guildServices,
|
||||
MusicService musicService,
|
||||
Supplier<JDA> jdaSupplier,
|
||||
Runnable shutdownHook
|
||||
) {
|
||||
this.settings = settings;
|
||||
this.guildServices = guildServices;
|
||||
this.musicService = musicService;
|
||||
this.jdaSupplier = jdaSupplier;
|
||||
this.shutdownHook = shutdownHook;
|
||||
}
|
||||
@@ -77,6 +81,7 @@ public class WebApi implements Lifecycle {
|
||||
ActionRoutes.register(config.routes, jdaSupplier, shutdownHook);
|
||||
SettingsRoutes.register(config.routes, jdaSupplier, guildServices);
|
||||
StatsRoutes.register(config.routes, jdaSupplier, guildServices);
|
||||
MusicRoutes.register(config.routes, jdaSupplier, musicService);
|
||||
|
||||
config.routes.exception(HttpResponseException.class, (e, ctx) ->
|
||||
ctx.status(e.getStatus()).json(Map.of("status", "error", "message", e.getMessage()))
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /stats/bot} response.
|
||||
*/
|
||||
public record BotStatsResponse(
|
||||
int guild_count,
|
||||
long gateway_ping,
|
||||
String status,
|
||||
long uptime_seconds
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for a {@code /guilds/channels/{guildId}} and {@code /guilds/roles/{guildId}}
|
||||
* entry.
|
||||
*/
|
||||
public record ChannelSummaryResponse(
|
||||
String channel_id,
|
||||
String channel_name
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /music/{guildId}/skip}, {@code /music/{guildId}/stop}, and
|
||||
* {@code /music/{guildId}/vote-skip/{userId}} responses.
|
||||
*/
|
||||
public record CommandResultResponse(
|
||||
String status,
|
||||
String message
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared OpenAPI schema for the error body written by the {@code HttpResponseException} handler
|
||||
* in {@link me.bobert.netneutrality.http.server.WebApi}.
|
||||
*/
|
||||
public record ErrorResponse(
|
||||
String status,
|
||||
String message
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for a {@code /guilds/owner} entry.
|
||||
*/
|
||||
public record GuildOwnerEntryResponse(
|
||||
String guild_id,
|
||||
String owner_id
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /stats/guilds/{guildId}} response.
|
||||
*/
|
||||
public record GuildStatsResponse(
|
||||
String guild_id,
|
||||
int member_count,
|
||||
int channel_count,
|
||||
int role_count,
|
||||
boolean debug_enabled,
|
||||
int blacklist_size
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for a {@code /guilds} entry.
|
||||
*/
|
||||
public record GuildSummaryResponse(
|
||||
String guild_id,
|
||||
String name,
|
||||
String icon_url,
|
||||
int member_count,
|
||||
String owner_id
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /guilds/members/{guildId}/{userId}/roles} response.
|
||||
*/
|
||||
public record MemberRolesResponse(
|
||||
String guild_id,
|
||||
String user_id,
|
||||
RoleSummaryResponse[] roles
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /guilds/members/{guildId}/{userId}} response.
|
||||
*/
|
||||
public record MemberSummaryResponse(
|
||||
String user_id,
|
||||
String username,
|
||||
String nickname,
|
||||
String[] role_ids
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /music/{guildId}/status} response.
|
||||
*/
|
||||
public record MusicStatusResponse(
|
||||
boolean connected,
|
||||
String channel_id,
|
||||
String channel_name,
|
||||
boolean playing,
|
||||
TrackSummaryResponse current_track,
|
||||
long position_ms,
|
||||
int queue_size
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /guilds/owner/{guildId}} response.
|
||||
*/
|
||||
public record OwnerResponse(
|
||||
String guild_id,
|
||||
String owner_id
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for a {@code /music/{guildId}/queue} entry.
|
||||
*/
|
||||
public record QueuedTrackResponse(
|
||||
String title,
|
||||
String author,
|
||||
String uri,
|
||||
long duration_ms,
|
||||
String requester_id,
|
||||
int skip_count
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for a role entry of the {@code /guilds/members/{guildId}/{userId}/roles}
|
||||
* response.
|
||||
*/
|
||||
public record RoleSummaryResponse(
|
||||
String role_id,
|
||||
String role_name
|
||||
) {
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for a {@code /settings/catalog} entry.
|
||||
*/
|
||||
public record SettingCatalogEntryResponse(
|
||||
String name,
|
||||
String default_value,
|
||||
String description,
|
||||
String type,
|
||||
String[] allowed_values
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /settings/current/{guildId}} response.
|
||||
*/
|
||||
public record SettingsCurrentResponse(
|
||||
String status,
|
||||
Map<String, String> settings
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /settings/reload/{guildId}} response.
|
||||
*/
|
||||
public record SettingsReloadResponse(
|
||||
boolean status
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /settings/current/{guildId}/{name}} request body.
|
||||
*/
|
||||
public record SettingsUpdateRequest(
|
||||
String value
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /settings/current/{guildId}/{name}} response.
|
||||
*/
|
||||
public record SettingsUpdateResponse(
|
||||
String status,
|
||||
String name,
|
||||
String value
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /actions/status} response.
|
||||
*/
|
||||
public record StatusResponse(
|
||||
String health,
|
||||
String ping
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the currently playing track summary embedded in
|
||||
* {@code /music/{guildId}/status}.
|
||||
*/
|
||||
public record TrackSummaryResponse(
|
||||
String title,
|
||||
String author,
|
||||
String uri,
|
||||
long duration_ms
|
||||
) {
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /guilds/voice-channels/{guildId}} and
|
||||
* {@code /guilds/voice-channels/{guildId}/{channelId}} responses.
|
||||
*/
|
||||
public record VoiceChannelSummaryResponse(
|
||||
String channel_id,
|
||||
String channel_name,
|
||||
int user_limit,
|
||||
int member_count,
|
||||
VoiceMemberSummaryResponse[] members
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for a member entry connected to a voice channel.
|
||||
*/
|
||||
public record VoiceMemberSummaryResponse(
|
||||
String user_id,
|
||||
String username,
|
||||
String nickname
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package me.bobert.netneutrality.http.server.dto;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* OpenAPI schema for the {@code /music/{guildId}/volume} request body.
|
||||
*/
|
||||
public record VolumeRequest(
|
||||
int volume
|
||||
) {
|
||||
}
|
||||
@@ -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}/volume} response.
|
||||
*/
|
||||
public record VolumeResponse(
|
||||
String status,
|
||||
int volume
|
||||
) {
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.GuildVoiceState;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -75,6 +76,18 @@ public class MusicManager implements MusicManagerCallback {
|
||||
return scheduler.getQueueManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vote-skip service for this guild.
|
||||
*
|
||||
* <p>Exposed separately since vote-skipping is application logic that has no equivalent on
|
||||
* the Lavalink API.
|
||||
*
|
||||
* @return the vote-skip service
|
||||
*/
|
||||
public VoteSkipService getVoteSkipService() {
|
||||
return voteSkipService;
|
||||
}
|
||||
|
||||
public Guild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
@@ -213,7 +226,7 @@ public class MusicManager implements MusicManagerCallback {
|
||||
}
|
||||
|
||||
public synchronized void stopTrack() {
|
||||
updatePlayer("stop track", builder -> builder.stopTrack());
|
||||
updatePlayer("stop track", PlayerUpdateBuilder::stopTrack);
|
||||
}
|
||||
|
||||
public synchronized CommandExecutionResult resume() {
|
||||
@@ -221,8 +234,16 @@ public class MusicManager implements MusicManagerCallback {
|
||||
}
|
||||
|
||||
public synchronized CommandExecutionResult voteSkip(CommandContext context) {
|
||||
return voteSkip(context.getGuildUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #voteSkip(CommandContext)} but driven directly by a {@link Member}, so it
|
||||
* can be invoked from non-command callers such as the REST API.
|
||||
*/
|
||||
public synchronized CommandExecutionResult voteSkip(Member member) {
|
||||
int threshold = parseVoteSkipThreshold(guildService);
|
||||
return voteSkipService.voteSkip(context, getQueueManager(), getConnectedChannel(), isCurrentlyPlaying(), threshold);
|
||||
return voteSkipService.voteSkip(member, guild.getId(), getQueueManager(), getConnectedChannel(), isCurrentlyPlaying(), threshold);
|
||||
}
|
||||
|
||||
private static int parseVoteSkipThreshold(GuildService guildService) {
|
||||
|
||||
@@ -8,6 +8,7 @@ package me.bobert.netneutrality.music;
|
||||
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -37,6 +38,15 @@ public class VoteSkipService {
|
||||
* configurable vote threshold percentage of connected listeners.
|
||||
*/
|
||||
public CommandExecutionResult voteSkip(CommandContext context, QueueManager queueManager, AudioChannel audioChannel, boolean isCurrentlyPlaying, int thresholdPercent) {
|
||||
return voteSkip(context.getGuildUser(), context.getGuild().getId(), queueManager, audioChannel, isCurrentlyPlaying, thresholdPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #voteSkip(CommandContext, QueueManager, AudioChannel, boolean, int)} but
|
||||
* driven directly by a {@link Member}, so it can be invoked from non-command callers such as
|
||||
* the REST API, which has no {@link CommandContext} to work with.
|
||||
*/
|
||||
public CommandExecutionResult voteSkip(Member voter, String guildId, QueueManager queueManager, AudioChannel audioChannel, boolean isCurrentlyPlaying, int thresholdPercent) {
|
||||
if (audioChannel == null) {
|
||||
return CommandExecutionResult.warn("Bot is not connected to a voice channel");
|
||||
}
|
||||
@@ -60,8 +70,8 @@ public class VoteSkipService {
|
||||
|
||||
double needed = connected * (thresholdPercent / 100.0);
|
||||
|
||||
info.addSkip(context.getGuildUser());
|
||||
logger.debug("VoteSkip: guild={}, connected={}, needed={}, skips={}, user={}", context.getGuild().getId(), connected, needed, info.getSkipCount(), context.getGuildUser().getId());
|
||||
info.addSkip(voter);
|
||||
logger.debug("VoteSkip: guild={}, connected={}, needed={}, skips={}, user={}", guildId, connected, needed, info.getSkipCount(), voter.getId());
|
||||
|
||||
if (info.getSkipCount() > needed) {
|
||||
return queueManager.skip();
|
||||
|
||||
@@ -10,6 +10,7 @@ import me.bobert.netneutrality.db.service.BlacklistService;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.db.service.GuildSettingService;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
@@ -82,7 +83,9 @@ class WebApiTest {
|
||||
when(settings.get("NETNEUTRALITY_API_PORT", "9000")).thenReturn(String.valueOf(port));
|
||||
when(settings.get("NETNEUTRALITY_API_KEY")).thenReturn(API_KEY);
|
||||
|
||||
webApi = new WebApi(settings, provider, () -> jda, () -> { });
|
||||
MusicService musicService = mock(MusicService.class);
|
||||
|
||||
webApi = new WebApi(settings, provider, musicService, () -> jda, () -> { });
|
||||
webApi.start();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user