Skip to content

Commit

Permalink
feat(engines): add isResponsible method
Browse files Browse the repository at this point in the history
BREAKING CHANGE: remove exported function `detectTrackSource`
BREAKING CHANGE: require method  `isResponsible` for engines
  • Loading branch information
larsrickert committed Jun 7, 2022
1 parent bf8def3 commit dc58e90
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 62 deletions.
54 changes: 26 additions & 28 deletions src/engines/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,45 @@ import { isSubPath } from "../utils/fs";

/**
* Player engine to search/stream tracks that are stored in the file system.
* Search is currently not supported.
*/
export const fileEngine: PlayerEngine = {
search: async (query, playerOptions) => {
if (playerOptions.fileRoot && !isSubPath(playerOptions.fileRoot, query)) {
return [];
async isResponsible(query, { fileRoot }) {
if (fileRoot && !isSubPath(fileRoot, query)) {
return false;
}

// check if query is file path
try {
const stats = await stat(query);
if (!stats.isFile()) return [];

return [
{
tracks: [
{
title: path.basename(query),
duration: 0,
url: query,
source: "file",
},
],
source: "file",
},
];
return stats.isFile();
} catch (e) {
return false;
}
},
async search(query, playerOptions) {
if (!(await this.isResponsible(query, playerOptions))) {
return [];
}

return [
{
tracks: [
{
title: path.basename(query),
duration: 0,
url: query,
source: "file",
},
],
source: "file",
},
];
},
getStream: async (track, playerOptions) => {
if (
playerOptions.fileRoot &&
!isSubPath(playerOptions.fileRoot, track.url)
) {
async getStream(track, playerOptions) {
if (!(await this.isResponsible(track.url, playerOptions))) {
return null;
}

return {
stream: track.url,
type: StreamType.Arbitrary,
};
return { stream: track.url, type: StreamType.Arbitrary };
},
};
27 changes: 1 addition & 26 deletions src/engines/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,10 @@
import { stat } from "fs/promises";
import { PlayerEngine, TrackSource } from "../types/engines";
import { isSubPath } from "../utils/fs";
import { fileEngine } from "./file";
import { spotifyEngine } from "./spotify";
import { youtubeEngine } from "./youtube";

export const playerEngines: Record<TrackSource, PlayerEngine> = {
youtube: youtubeEngine,
file: fileEngine,
spotify: spotifyEngine,
file: fileEngine,
};

/**
* Automatically detect the query type
*/
export async function detectTrackSource(
query: string,
fileRoot?: string
): Promise<TrackSource> {
if (query.startsWith("https://open.spotify.com/")) return "spotify";
if (query.startsWith("https://www.youtube.com/")) return "youtube";

if (!fileRoot || isSubPath(fileRoot, query)) {
// check if query is file path
try {
const stats = await stat(query);
if (stats.isFile()) return "file";
} catch (e) {
// noop
}
}

return "youtube";
}
7 changes: 5 additions & 2 deletions src/engines/spotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { youtubeEngine } from "./youtube";
* Spotify does not provide a web api to stream tracks so the track will be streamed from YouTube instead.
*/
export const spotifyEngine: PlayerEngine = {
search: async (query) => {
async isResponsible(query) {
return query.startsWith("https://open.spotify.com");
},
async search(query) {
const isPlaylist = query.startsWith("https://open.spotify.com/playlist");
if (isPlaylist) return await searchPlaylist(query);

Expand All @@ -20,7 +23,7 @@ export const spotifyEngine: PlayerEngine = {
},
];
},
getStream: async (track, playerOptions, streamOptions) => {
async getStream(track, playerOptions, streamOptions) {
const searchResults = await youtubeEngine.search(
`${track.title} ${track.artist}`,
playerOptions,
Expand Down
7 changes: 5 additions & 2 deletions src/engines/youtube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { PlayerEngine, Playlist, SearchResult, Track } from "../types/engines";
* Player engine to search/stream tracks from YouTube.
*/
export const youtubeEngine: PlayerEngine = {
search: async (query, _, searchOptions) => {
async isResponsible(query) {
return query.startsWith("https://www.youtube.com");
},
async search(query, _, searchOptions) {
const isPlaylist =
query.startsWith("https://www.youtube.com/watch") &&
query.includes("list=");
Expand All @@ -26,7 +29,7 @@ export const youtubeEngine: PlayerEngine = {
},
];
},
getStream: async (track, playerOptions, streamOptions) => {
async getStream(track, playerOptions, streamOptions) {
return await playdl.stream(track.url, {
quality:
playerOptions.quality === "low"
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export { createSkipCommand } from "./commands/skip";
export { createSongCommand } from "./commands/song";
export { createStopCommand } from "./commands/stop";
export { createSetVolumeCommand } from "./commands/volume";
export { detectTrackSource } from "./engines";
export * from "./player";
export * from "./player-manager";
export * from "./types/commands";
Expand Down
21 changes: 18 additions & 3 deletions src/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ import {
} from "@discordjs/voice";
import { VoiceBasedChannel } from "discord.js";
import { TypedEmitter } from "tiny-typed-emitter";
import { detectTrackSource, playerEngines } from "./engines";
import { SearchOptions, SearchResult, Track } from "./types/engines";
import { playerEngines } from "./engines";
import {
SearchOptions,
SearchResult,
Track,
TrackSource,
} from "./types/engines";
import {
AudioPlayerMetadata,
PlayerEvents,
Expand Down Expand Up @@ -285,6 +290,16 @@ export class Player extends TypedEmitter<PlayerEvents> {
return this.audioResource?.playbackDuration ?? 0;
}

private async detectTrackSource(query: string): Promise<TrackSource> {
for (const [source, engine] of Object.entries(playerEngines)) {
if (await engine.isResponsible(query, this.options)) {
return source as TrackSource;
}
}

return "youtube";
}

/**
* Searches tracks for the given query.
*
Expand All @@ -299,7 +314,7 @@ export class Player extends TypedEmitter<PlayerEvents> {
if (customResult) return customResult;
}

const trackSource = await detectTrackSource(query, this.options.fileRoot);
const trackSource = await this.detectTrackSource(query);
const playerEngine = playerEngines[trackSource];
return await playerEngine.search(query, this.options, options);
}
Expand Down
1 change: 1 addition & 0 deletions src/types/engines.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PlayerOptions, StreamOptions, TrackStream } from "./player";

export interface PlayerEngine {
isResponsible(query: string, playerOptions: PlayerOptions): Promise<boolean>;
search(
query: string,
playerOptions: PlayerOptions,
Expand Down

0 comments on commit dc58e90

Please sign in to comment.