feat: add play-dl search resolver
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
59
src/media/playDlResolver.ts
Normal file
59
src/media/playDlResolver.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import play from "play-dl";
|
||||||
|
|
||||||
|
export interface PlayDlResult {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlayDlSearchResult {
|
||||||
|
title?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpotifyTrackLike {
|
||||||
|
type?: string;
|
||||||
|
name?: string;
|
||||||
|
artists?: Array<{ name?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchFunction = (
|
||||||
|
query: string,
|
||||||
|
options: { limit: number },
|
||||||
|
) => Promise<PlayDlSearchResult[]>;
|
||||||
|
|
||||||
|
type SpotifyFunction = (url: string) => Promise<SpotifyTrackLike>;
|
||||||
|
|
||||||
|
export interface PlayDlDependencies {
|
||||||
|
search?: SearchFunction;
|
||||||
|
spotify?: SpotifyFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPlayDlResolver(dependencies: PlayDlDependencies = {}) {
|
||||||
|
const search: SearchFunction = dependencies.search ?? play.search;
|
||||||
|
const spotify: SpotifyFunction = dependencies.spotify ?? (play.spotify as SpotifyFunction);
|
||||||
|
|
||||||
|
return {
|
||||||
|
async searchYouTube(query: string): Promise<PlayDlResult> {
|
||||||
|
const results = await search(query, { limit: 1 });
|
||||||
|
const first = results[0];
|
||||||
|
if (!first?.url) throw new Error(`No YouTube result found for ${query}`);
|
||||||
|
return {
|
||||||
|
title: first.title || query,
|
||||||
|
url: first.url,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async resolveSpotifyTrack(url: string): Promise<PlayDlResult> {
|
||||||
|
const track = await spotify(url);
|
||||||
|
if (track.type !== "track") {
|
||||||
|
throw new Error("Only Spotify track URLs are supported");
|
||||||
|
}
|
||||||
|
const artists = (track.artists || [])
|
||||||
|
.map((artist) => artist.name)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
const query = `${artists} ${track.name || ""} audio`.trim();
|
||||||
|
return this.searchYouTube(query);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
49
tests/media/playDlResolver.test.ts
Normal file
49
tests/media/playDlResolver.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { createPlayDlResolver } from "../../src/media/playDlResolver";
|
||||||
|
|
||||||
|
describe("createPlayDlResolver", () => {
|
||||||
|
it("returns the first YouTube search result", async () => {
|
||||||
|
const resolver = createPlayDlResolver({
|
||||||
|
search: vi.fn(async () => [
|
||||||
|
{ title: "Song Result", url: "https://youtube.com/watch?v=abc" },
|
||||||
|
]),
|
||||||
|
spotify: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(resolver.searchYouTube("artist song")).resolves.toEqual({
|
||||||
|
title: "Song Result",
|
||||||
|
url: "https://youtube.com/watch?v=abc",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("turns Spotify track metadata into a YouTube search query", async () => {
|
||||||
|
const resolver = createPlayDlResolver({
|
||||||
|
search: vi.fn(async () => [
|
||||||
|
{ title: "Artist - Track", url: "https://youtube.com/watch?v=track" },
|
||||||
|
]),
|
||||||
|
spotify: vi.fn(async () => ({
|
||||||
|
type: "track",
|
||||||
|
name: "Track",
|
||||||
|
artists: [{ name: "Artist" }],
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
resolver.resolveSpotifyTrack("https://open.spotify.com/track/123"),
|
||||||
|
).resolves.toEqual({
|
||||||
|
title: "Artist - Track",
|
||||||
|
url: "https://youtube.com/watch?v=track",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects Spotify playlists in this phase", async () => {
|
||||||
|
const resolver = createPlayDlResolver({
|
||||||
|
search: vi.fn(),
|
||||||
|
spotify: vi.fn(async () => ({ type: "playlist", name: "Playlist" })),
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
resolver.resolveSpotifyTrack("https://open.spotify.com/playlist/123"),
|
||||||
|
).rejects.toThrow("Only Spotify track URLs are supported");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user