mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42:50 +08:00
feat(spotify): report resolved backend + binaryAvailable on /status; share backend resolver [S4.2]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b672d76634
commit
6a72833c2a
6 files changed
+79
-21
No files matched your search
@@ -0,0 +1,12 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { resolveSpotifyBackendKind as pick } from "./backend-select.js";
|
||||||
|
describe("resolveSpotifyBackendKind", () => {
|
||||||
|
it("auto: go present -> go-librespot", () => expect(pick("auto", true, true)).toBe("go-librespot"));
|
||||||
|
it("auto: go absent, rust present -> librespot", () => expect(pick("auto", false, true)).toBe("librespot"));
|
||||||
|
it("auto: neither -> null", () => expect(pick("auto", false, false)).toBeNull());
|
||||||
|
it("go-librespot: present -> go-librespot", () => expect(pick("go-librespot", true, true)).toBe("go-librespot"));
|
||||||
|
it("go-librespot: absent -> null even if rust present", () => expect(pick("go-librespot", false, true)).toBeNull());
|
||||||
|
it("librespot: present -> librespot", () => expect(pick("librespot", true, true)).toBe("librespot"));
|
||||||
|
it("librespot: absent -> null even if go present", () => expect(pick("librespot", true, false)).toBeNull());
|
||||||
|
it("auto default fallthrough matches auto", () => expect(pick("auto", true, false)).toBe("go-librespot"));
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/** Which concrete backend runs for a given config + host binary availability. */
|
||||||
|
export type SpotifyBackendKind = "go-librespot" | "librespot";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure backend selection shared by SpotifyController.chooseBackend() (per-bot)
|
||||||
|
* and the web /status endpoint (process-wide). Booleans in, no IO — the caller
|
||||||
|
* supplies platform+binary presence.
|
||||||
|
*/
|
||||||
|
export function resolveSpotifyBackendKind(
|
||||||
|
backend: "auto" | "go-librespot" | "librespot",
|
||||||
|
goPresent: boolean,
|
||||||
|
rustPresent: boolean,
|
||||||
|
): SpotifyBackendKind | null {
|
||||||
|
switch (backend) {
|
||||||
|
case "go-librespot":
|
||||||
|
return goPresent ? "go-librespot" : null;
|
||||||
|
case "librespot":
|
||||||
|
return rustPresent ? "librespot" : null;
|
||||||
|
case "auto":
|
||||||
|
default:
|
||||||
|
if (goPresent) return "go-librespot";
|
||||||
|
if (rustPresent) return "librespot";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,9 +29,11 @@ import {
|
|||||||
type OAuthTokenStore,
|
type OAuthTokenStore,
|
||||||
} from "./spotify-oauth.js";
|
} from "./spotify-oauth.js";
|
||||||
import { SpotifyConnectApi } from "./connect-api.js";
|
import { SpotifyConnectApi } from "./connect-api.js";
|
||||||
|
import {
|
||||||
/** Which concrete backend the controller will run for this host + config. */
|
resolveSpotifyBackendKind,
|
||||||
export type SpotifyBackendKind = "go-librespot" | "librespot";
|
type SpotifyBackendKind,
|
||||||
|
} from "./backend-select.js";
|
||||||
|
export type { SpotifyBackendKind }; // keep the name exported for existing importers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal file-backed OAuth token store used when the caller does not inject a
|
* Minimal file-backed OAuth token store used when the caller does not inject a
|
||||||
@@ -156,17 +158,11 @@ export class SpotifyController extends EventEmitter {
|
|||||||
* librespot present, else null.
|
* librespot present, else null.
|
||||||
*/
|
*/
|
||||||
chooseBackend(): SpotifyBackendKind | null {
|
chooseBackend(): SpotifyBackendKind | null {
|
||||||
switch (this.config.backend) {
|
return resolveSpotifyBackendKind(
|
||||||
case "go-librespot":
|
this.config.backend,
|
||||||
return this.goPresent() ? "go-librespot" : null;
|
this.goPresent(),
|
||||||
case "librespot":
|
this.rustPresent(),
|
||||||
return this.rustPresent() ? "librespot" : null;
|
);
|
||||||
case "auto":
|
|
||||||
default:
|
|
||||||
if (this.goPresent()) return "go-librespot";
|
|
||||||
if (this.rustPresent()) return "librespot";
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** enabled in config AND a backend is selectable (platform + binary present). */
|
/** enabled in config AND a backend is selectable (platform + binary present). */
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function makeApp(oauth: SpotifyOAuthLike, role: Role = "admin", caps: string[] =
|
|||||||
createSpotifyRouter({
|
createSpotifyRouter({
|
||||||
oauth,
|
oauth,
|
||||||
logger: pino({ level: "silent" }),
|
logger: pino({ level: "silent" }),
|
||||||
getBackendInfo: () => ({ backend: "librespot", deviceName: "TS-Bot" }),
|
getBackendInfo: () => ({ backend: "librespot", deviceName: "TS-Bot", binaryAvailable: true }),
|
||||||
webUiRedirect: "/",
|
webUiRedirect: "/",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -84,7 +84,7 @@ describe("spotify OAuth router", () => {
|
|||||||
const app = makeApp(fakeOauth({ isAuthorized: () => true }));
|
const app = makeApp(fakeOauth({ isAuthorized: () => true }));
|
||||||
const res = await request(app).get("/api/spotify/status");
|
const res = await request(app).get("/api/spotify/status");
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
expect(res.body).toEqual({ authorized: true, backend: "librespot", deviceName: "TS-Bot" });
|
expect(res.body).toEqual({ authorized: true, backend: "librespot", deviceName: "TS-Bot", binaryAvailable: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("GET /status is 403 for a guest", async () => {
|
it("GET /status is 403 for a guest", async () => {
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ export interface SpotifyRouterOptions {
|
|||||||
oauth: SpotifyOAuthLike;
|
oauth: SpotifyOAuthLike;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
/** Process-wide backend info for /status (single Premium account, Stage 3). */
|
/** Process-wide backend info for /status (single Premium account, Stage 3). */
|
||||||
getBackendInfo: () => { backend: string; deviceName: string };
|
getBackendInfo: () => {
|
||||||
|
backend: string;
|
||||||
|
deviceName: string;
|
||||||
|
binaryAvailable: boolean;
|
||||||
|
};
|
||||||
/** Web UI page to bounce the browser back to after the OAuth callback. */
|
/** Web UI page to bounce the browser back to after the OAuth callback. */
|
||||||
webUiRedirect?: string;
|
webUiRedirect?: string;
|
||||||
}
|
}
|
||||||
@@ -67,6 +71,7 @@ export function createSpotifyRouter(opts: SpotifyRouterOptions): Router {
|
|||||||
authorized: oauth.isAuthorized(),
|
authorized: oauth.isAuthorized(),
|
||||||
backend: info.backend,
|
backend: info.backend,
|
||||||
deviceName: info.deviceName,
|
deviceName: info.deviceName,
|
||||||
|
binaryAvailable: info.binaryAvailable,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+24
-4
@@ -21,6 +21,14 @@ import { createAuditRouter } from "./api/audit.js";
|
|||||||
import { createFavoritesRouter } from "./api/favorites.js";
|
import { createFavoritesRouter } from "./api/favorites.js";
|
||||||
import { createSpotifyRouter } from "./api/spotify.js";
|
import { createSpotifyRouter } from "./api/spotify.js";
|
||||||
import type { SpotifyOAuth } from "../music/spotify/spotify-oauth.js";
|
import type { SpotifyOAuth } from "../music/spotify/spotify-oauth.js";
|
||||||
|
import { resolveSpotifyBackendKind } from "../music/spotify/backend-select.js";
|
||||||
|
import {
|
||||||
|
isGoLibrespotSupported,
|
||||||
|
findGoLibrespot,
|
||||||
|
isRustLibrespotSupported,
|
||||||
|
findLibrespot,
|
||||||
|
} from "../music/spotify/binary.js";
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
import { setupWebSocket } from "./websocket.js";
|
import { setupWebSocket } from "./websocket.js";
|
||||||
import { createUserStore } from "../data/users.js";
|
import { createUserStore } from "../data/users.js";
|
||||||
import { createSessionStore } from "../data/sessions.js";
|
import { createSessionStore } from "../data/sessions.js";
|
||||||
@@ -147,10 +155,22 @@ export function createWebServer(options: WebServerOptions): WebServer {
|
|||||||
createSpotifyRouter({
|
createSpotifyRouter({
|
||||||
oauth: options.spotifyOAuth,
|
oauth: options.spotifyOAuth,
|
||||||
logger,
|
logger,
|
||||||
getBackendInfo: () => ({
|
getBackendInfo: () => {
|
||||||
backend: options.config.spotify.backend,
|
const goPresent =
|
||||||
deviceName: options.config.spotify.deviceName,
|
isGoLibrespotSupported() && existsSync(findGoLibrespot());
|
||||||
}),
|
const rustPresent =
|
||||||
|
isRustLibrespotSupported() && existsSync(findLibrespot());
|
||||||
|
const resolved = resolveSpotifyBackendKind(
|
||||||
|
options.config.spotify.backend,
|
||||||
|
goPresent,
|
||||||
|
rustPresent,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
backend: resolved ?? "none",
|
||||||
|
deviceName: options.config.spotify.deviceName,
|
||||||
|
binaryAvailable: resolved !== null,
|
||||||
|
};
|
||||||
|
},
|
||||||
webUiRedirect: "/",
|
webUiRedirect: "/",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user