mirror of
https://github.com/BluemediaGER/muse.git
synced 2024-11-23 01:05:30 +01:00
Refine autocomplete
This commit is contained in:
parent
7901fcce3d
commit
09665af53e
|
@ -4,6 +4,7 @@ import {Except} from 'type-fest';
|
||||||
import {SlashCommandBuilder} from '@discordjs/builders';
|
import {SlashCommandBuilder} from '@discordjs/builders';
|
||||||
import shuffle from 'array-shuffle';
|
import shuffle from 'array-shuffle';
|
||||||
import {inject, injectable} from 'inversify';
|
import {inject, injectable} from 'inversify';
|
||||||
|
import Spotify from 'spotify-web-api-node';
|
||||||
import Command from '.';
|
import Command from '.';
|
||||||
import {TYPES} from '../types.js';
|
import {TYPES} from '../types.js';
|
||||||
import {QueuedSong, STATUS} from '../services/player.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 errorMsg from '../utils/error-msg.js';
|
||||||
import GetSongs from '../services/get-songs.js';
|
import GetSongs from '../services/get-songs.js';
|
||||||
import {prisma} from '../utils/db.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()
|
@injectable()
|
||||||
export default class implements Command {
|
export default class implements Command {
|
||||||
|
@ -35,10 +39,14 @@ export default class implements Command {
|
||||||
|
|
||||||
private readonly playerManager: PlayerManager;
|
private readonly playerManager: PlayerManager;
|
||||||
private readonly getSongs: GetSongs;
|
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.playerManager = playerManager;
|
||||||
this.getSongs = getSongs;
|
this.getSongs = getSongs;
|
||||||
|
this.spotify = thirdParty.spotify;
|
||||||
|
this.cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line complexity
|
// eslint-disable-next-line complexity
|
||||||
|
@ -201,9 +209,16 @@ export default class implements Command {
|
||||||
return interaction.respond([]);
|
return interaction.respond([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await interaction.respond((await getYouTubeSuggestionsFor(query)).map(s => ({
|
const suggestions = await this.cache.wrap(
|
||||||
name: s,
|
getYouTubeAndSpotifySuggestionsFor,
|
||||||
value: s,
|
query,
|
||||||
})));
|
this.spotify,
|
||||||
|
10,
|
||||||
|
{
|
||||||
|
expiresIn: ONE_HOUR_IN_SECONDS,
|
||||||
|
key: `autocomplete:${query}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.respond(suggestions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,10 @@ import {cleanUrl} from '../utils/url.js';
|
||||||
import ThirdParty from './third-party.js';
|
import ThirdParty from './third-party.js';
|
||||||
import Config from './config.js';
|
import Config from './config.js';
|
||||||
import KeyValueCacheProvider from './key-value-cache.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'>;
|
type QueuedSongWithoutChannel = Except<QueuedSong, 'addedInChannelId'>;
|
||||||
|
|
||||||
const ONE_HOUR_IN_SECONDS = 60 * 60;
|
|
||||||
const ONE_MINUTE_IN_SECONDS = 1 * 60;
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class {
|
export default class {
|
||||||
private readonly youtube: YouTube;
|
private readonly youtube: YouTube;
|
||||||
|
|
2
src/utils/constants.ts
Normal file
2
src/utils/constants.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const ONE_HOUR_IN_SECONDS = 60 * 60;
|
||||||
|
export const ONE_MINUTE_IN_SECONDS = 1 * 60;
|
70
src/utils/get-youtube-and-spotify-suggestions-for.ts
Normal file
70
src/utils/get-youtube-and-spotify-suggestions-for.ts
Normal file
|
@ -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;
|
Loading…
Reference in a new issue