aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Isom <[email protected]>2022-01-21 19:57:51 -0600
committerMax Isom <[email protected]>2022-01-21 19:57:51 -0600
commit09665af53ee1b1903fc9ea719722aa5dfdc26325 (patch)
tree736b0a3ee648e54e6e518fbca150a4cd27ee226b
parent7901fcce3d7d0ce2ec91ba36a3ba9107e8709bff (diff)
downloadmuse-09665af53ee1b1903fc9ea719722aa5dfdc26325.tar.xz
muse-09665af53ee1b1903fc9ea719722aa5dfdc26325.zip
Refine autocomplete
-rw-r--r--src/commands/play.ts27
-rw-r--r--src/services/get-songs.ts4
-rw-r--r--src/utils/constants.ts2
-rw-r--r--src/utils/get-youtube-and-spotify-suggestions-for.ts70
4 files changed, 94 insertions, 9 deletions
diff --git a/src/commands/play.ts b/src/commands/play.ts
index 7e5dee1..5ef4c1d 100644
--- a/src/commands/play.ts
+++ b/src/commands/play.ts
@@ -4,6 +4,7 @@ import {Except} from 'type-fest';
import {SlashCommandBuilder} from '@discordjs/builders';
import shuffle from 'array-shuffle';
import {inject, injectable} from 'inversify';
+import Spotify from 'spotify-web-api-node';
import Command from '.';
import {TYPES} from '../types.js';
import {QueuedSong, STATUS} from '../services/player.js';
@@ -12,7 +13,10 @@ import {getMostPopularVoiceChannel, getMemberVoiceChannel} from '../utils/channe
import errorMsg from '../utils/error-msg.js';
import GetSongs from '../services/get-songs.js';
import {prisma} from '../utils/db.js';
-import getYouTubeSuggestionsFor from '../utils/get-youtube-suggestions-for.js';
+import ThirdParty from '../services/third-party.js';
+import getYouTubeAndSpotifySuggestionsFor from '../utils/get-youtube-and-spotify-suggestions-for.js';
+import KeyValueCacheProvider from '../services/key-value-cache.js';
+import {ONE_HOUR_IN_SECONDS} from '../utils/constants.js';
@injectable()
export default class implements Command {
@@ -35,10 +39,14 @@ export default class implements Command {
private readonly playerManager: PlayerManager;
private readonly getSongs: GetSongs;
+ private readonly spotify: Spotify;
+ private readonly cache: KeyValueCacheProvider;
- constructor(@inject(TYPES.Managers.Player) playerManager: PlayerManager, @inject(TYPES.Services.GetSongs) getSongs: GetSongs) {
+ constructor(@inject(TYPES.Managers.Player) playerManager: PlayerManager, @inject(TYPES.Services.GetSongs) getSongs: GetSongs, @inject(TYPES.ThirdParty) thirdParty: ThirdParty, @inject(TYPES.KeyValueCache) cache: KeyValueCacheProvider) {
this.playerManager = playerManager;
this.getSongs = getSongs;
+ this.spotify = thirdParty.spotify;
+ this.cache = cache;
}
// eslint-disable-next-line complexity
@@ -201,9 +209,16 @@ export default class implements Command {
return interaction.respond([]);
}
- await interaction.respond((await getYouTubeSuggestionsFor(query)).map(s => ({
- name: s,
- value: s,
- })));
+ const suggestions = await this.cache.wrap(
+ getYouTubeAndSpotifySuggestionsFor,
+ query,
+ this.spotify,
+ 10,
+ {
+ expiresIn: ONE_HOUR_IN_SECONDS,
+ key: `autocomplete:${query}`,
+ });
+
+ await interaction.respond(suggestions);
}
}
diff --git a/src/services/get-songs.ts b/src/services/get-songs.ts
index 780f7ec..996a638 100644
--- a/src/services/get-songs.ts
+++ b/src/services/get-songs.ts
@@ -15,12 +15,10 @@ import {cleanUrl} from '../utils/url.js';
import ThirdParty from './third-party.js';
import Config from './config.js';
import KeyValueCacheProvider from './key-value-cache.js';
+import {ONE_HOUR_IN_SECONDS, ONE_MINUTE_IN_SECONDS} from '../utils/constants.js';
type QueuedSongWithoutChannel = Except<QueuedSong, 'addedInChannelId'>;
-const ONE_HOUR_IN_SECONDS = 60 * 60;
-const ONE_MINUTE_IN_SECONDS = 1 * 60;
-
@injectable()
export default class {
private readonly youtube: YouTube;
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
new file mode 100644
index 0000000..70d9e2c
--- /dev/null
+++ b/src/utils/constants.ts
@@ -0,0 +1,2 @@
+export const ONE_HOUR_IN_SECONDS = 60 * 60;
+export const ONE_MINUTE_IN_SECONDS = 1 * 60;
diff --git a/src/utils/get-youtube-and-spotify-suggestions-for.ts b/src/utils/get-youtube-and-spotify-suggestions-for.ts
new file mode 100644
index 0000000..10f9394
--- /dev/null
+++ b/src/utils/get-youtube-and-spotify-suggestions-for.ts
@@ -0,0 +1,70 @@
+import {ApplicationCommandOptionChoice} from 'discord.js';
+import SpotifyWebApi from 'spotify-web-api-node';
+import getYouTubeSuggestionsFor from './get-youtube-suggestions-for.js';
+
+const filterDuplicates = <T extends {name: string}>(items: T[]) => {
+ const results: T[] = [];
+
+ for (const item of items) {
+ if (!results.some(result => result.name === item.name)) {
+ results.push(item);
+ }
+ }
+
+ return results;
+};
+
+const getYouTubeAndSpotifySuggestionsFor = async (query: string, spotify: SpotifyWebApi, limit = 10): Promise<ApplicationCommandOptionChoice[]> => {
+ const [youtubeSuggestions, spotifyResults] = await Promise.all([
+ getYouTubeSuggestionsFor(query),
+ spotify.search(query, ['track', 'album'], {limit: 5}),
+ ]);
+
+ const totalYouTubeResults = youtubeSuggestions.length;
+
+ const spotifyAlbums = filterDuplicates(spotifyResults.body.albums?.items ?? []);
+ const spotifyTracks = filterDuplicates(spotifyResults.body.tracks?.items ?? []);
+
+ const totalSpotifyResults = spotifyAlbums.length + spotifyTracks.length;
+
+ // Number of results for each source should be roughly the same.
+ // If we don't have enough Spotify suggestions, prioritize YouTube results.
+ const maxSpotifySuggestions = Math.floor(limit / 2);
+ const numOfSpotifySuggestions = Math.min(maxSpotifySuggestions, totalSpotifyResults);
+
+ const maxYouTubeSuggestions = limit - numOfSpotifySuggestions;
+ const numOfYouTubeSuggestions = Math.min(maxYouTubeSuggestions, totalYouTubeResults);
+
+ const suggestions: ApplicationCommandOptionChoice[] = [];
+
+ suggestions.push(
+ ...youtubeSuggestions
+ .slice(0, numOfYouTubeSuggestions)
+ .map(suggestion => ({
+ name: `YouTube: ${suggestion}`,
+ value: suggestion,
+ }),
+ ));
+
+ const maxSpotifyAlbums = Math.floor(numOfSpotifySuggestions / 2);
+ const numOfSpotifyAlbums = Math.min(maxSpotifyAlbums, spotifyResults.body.albums?.items.length ?? 0);
+ const maxSpotifyTracks = numOfSpotifySuggestions - numOfSpotifyAlbums;
+
+ suggestions.push(
+ ...spotifyAlbums.slice(0, maxSpotifyAlbums).map(album => ({
+ name: `Spotify: 💿 ${album.name}${album.artists.length > 0 ? ` - ${album.artists[0].name}` : ''}`,
+ value: `spotify:album:${album.id}`,
+ })),
+ );
+
+ suggestions.push(
+ ...spotifyTracks.slice(0, maxSpotifyTracks).map(track => ({
+ name: `Spotify: 🎵 ${track.name}${track.artists.length > 0 ? ` - ${track.artists[0].name}` : ''}`,
+ value: `spotify:track:${track.id}`,
+ })),
+ );
+
+ return suggestions;
+};
+
+export default getYouTubeAndSpotifySuggestionsFor;