mirror of
https://github.com/BluemediaGER/muse.git
synced 2024-11-23 09:15:29 +01:00
parent
da72cd708b
commit
59cbc8b474
|
@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Changed
|
||||||
|
- Queue embeds are now more detailed and appear when resuming playback. Thanks @bokherus!
|
||||||
|
|
||||||
## [0.4.0] - 2022-01-17
|
## [0.4.0] - 2022-01-17
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -112,7 +112,7 @@
|
||||||
"spotify-web-api-node": "^5.0.2",
|
"spotify-web-api-node": "^5.0.2",
|
||||||
"xbytes": "^1.7.0",
|
"xbytes": "^1.7.0",
|
||||||
"youtube.ts": "^0.2.5",
|
"youtube.ts": "^0.2.5",
|
||||||
"ytdl-core": "^4.9.2",
|
"ytdl-core": "^4.10.0",
|
||||||
"ytsr": "^3.5.3"
|
"ytsr": "^3.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import errorMsg from '../utils/error-msg.js';
|
||||||
import Command from '.';
|
import Command from '.';
|
||||||
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 {buildPlayingMessageEmbed} from '../utils/build-embed.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class implements Command {
|
export default class implements Command {
|
||||||
|
@ -57,6 +58,7 @@ export default class implements Command {
|
||||||
const res = new LoadingMessage(msg.channel as TextChannel);
|
const res = new LoadingMessage(msg.channel as TextChannel);
|
||||||
await res.start();
|
await res.start();
|
||||||
|
|
||||||
|
try {
|
||||||
const player = this.playerManager.get(msg.guild!.id);
|
const player = this.playerManager.get(msg.guild!.id);
|
||||||
|
|
||||||
const wasPlayingSong = player.getCurrent() !== null;
|
const wasPlayingSong = player.getCurrent() !== null;
|
||||||
|
@ -76,14 +78,18 @@ export default class implements Command {
|
||||||
await player.connect(targetVoiceChannel);
|
await player.connect(targetVoiceChannel);
|
||||||
await player.play();
|
await player.play();
|
||||||
|
|
||||||
await res.stop('the stop-and-go light is now green');
|
await Promise.all([
|
||||||
|
res.stop('the stop-and-go light is now green'),
|
||||||
|
msg.channel.send({embeds: [buildPlayingMessageEmbed(player)]}),
|
||||||
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const addToFrontOfQueue = args[args.length - 1] === 'i' || args[args.length - 1] === 'immediate';
|
const addToFrontOfQueue = args[args.length - 1] === 'i' || args[args.length - 1] === 'immediate';
|
||||||
const shuffleAdditions = args[args.length - 1] === 's' || args[args.length - 1] === 'shuffle';
|
const shuffleAdditions = args[args.length - 1] === 's' || args[args.length - 1] === 'shuffle';
|
||||||
|
|
||||||
let newSongs: Array<Except<QueuedSong, 'addedInChannelId'>> = [];
|
let newSongs: Array<Except<QueuedSong, 'addedInChannelId' | 'requestedBy'>> = [];
|
||||||
let extraMsg = '';
|
let extraMsg = '';
|
||||||
|
|
||||||
// Test if it's a complete URL
|
// Test if it's a complete URL
|
||||||
|
@ -159,7 +165,7 @@ export default class implements Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
newSongs.forEach(song => {
|
newSongs.forEach(song => {
|
||||||
player.add({...song, addedInChannelId: msg.channel.id}, {immediate: addToFrontOfQueue});
|
player.add({...song, addedInChannelId: msg.channel.id, requestedBy: msg.author.id}, {immediate: addToFrontOfQueue});
|
||||||
});
|
});
|
||||||
|
|
||||||
const firstSong = newSongs[0];
|
const firstSong = newSongs[0];
|
||||||
|
@ -175,6 +181,8 @@ export default class implements Command {
|
||||||
if (wasPlayingSong) {
|
if (wasPlayingSong) {
|
||||||
statusMsg = 'resuming playback';
|
statusMsg = 'resuming playback';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await msg.channel.send({embeds: [buildPlayingMessageEmbed(player)]});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build response message
|
// Build response message
|
||||||
|
@ -195,5 +203,9 @@ export default class implements Command {
|
||||||
} else {
|
} else {
|
||||||
await res.stop(`u betcha, **${firstSong.title}** and ${newSongs.length - 1} other songs were added to the queue${extraMsg}`);
|
await res.stop(`u betcha, **${firstSong.title}** and ${newSongs.length - 1} other songs were added to the queue${extraMsg}`);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await res.stop();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
import {Message, MessageEmbed} from 'discord.js';
|
import {Message} from 'discord.js';
|
||||||
import getYouTubeID from 'get-youtube-id';
|
|
||||||
import {inject, injectable} from 'inversify';
|
import {inject, injectable} from 'inversify';
|
||||||
import {TYPES} from '../types.js';
|
import {TYPES} from '../types.js';
|
||||||
import PlayerManager from '../managers/player.js';
|
import PlayerManager from '../managers/player.js';
|
||||||
import {STATUS} from '../services/player.js';
|
|
||||||
import Command from '.';
|
import Command from '.';
|
||||||
import getProgressBar from '../utils/get-progress-bar.js';
|
import {buildQueueEmbed} from '../utils/build-embed.js';
|
||||||
import errorMsg from '../utils/error-msg.js';
|
|
||||||
import {prettyTime} from '../utils/time.js';
|
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class implements Command {
|
export default class implements Command {
|
||||||
|
@ -29,54 +23,8 @@ export default class implements Command {
|
||||||
public async execute(msg: Message, args: string []): Promise<void> {
|
public async execute(msg: Message, args: string []): Promise<void> {
|
||||||
const player = this.playerManager.get(msg.guild!.id);
|
const player = this.playerManager.get(msg.guild!.id);
|
||||||
|
|
||||||
const currentlyPlaying = player.getCurrent();
|
const embed = buildQueueEmbed(player, args[0] ? parseInt(args[0], 10) : 1);
|
||||||
|
|
||||||
if (currentlyPlaying) {
|
|
||||||
const queueSize = player.queueSize();
|
|
||||||
const queuePage = args[0] ? parseInt(args[0], 10) : 1;
|
|
||||||
|
|
||||||
const maxQueuePage = Math.ceil((queueSize + 1) / PAGE_SIZE);
|
|
||||||
|
|
||||||
if (queuePage > maxQueuePage) {
|
|
||||||
await msg.channel.send(errorMsg('the queue isn\'t that big'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new MessageEmbed();
|
|
||||||
|
|
||||||
embed.setTitle(currentlyPlaying.title);
|
|
||||||
embed.setURL(`https://www.youtube.com/watch?v=${currentlyPlaying.url.length === 11 ? currentlyPlaying.url : getYouTubeID(currentlyPlaying.url) ?? ''}`);
|
|
||||||
|
|
||||||
let description = player.status === STATUS.PLAYING ? '⏹️' : '▶️';
|
|
||||||
description += ' ';
|
|
||||||
description += getProgressBar(20, player.getPosition() / currentlyPlaying.length);
|
|
||||||
description += ' ';
|
|
||||||
description += `\`[${prettyTime(player.getPosition())}/${currentlyPlaying.isLive ? 'live' : prettyTime(currentlyPlaying.length)}]\``;
|
|
||||||
description += ' 🔉';
|
|
||||||
description += player.isQueueEmpty() ? '' : '\n\n**Next up:**';
|
|
||||||
|
|
||||||
embed.setDescription(description);
|
|
||||||
|
|
||||||
let footer = `Source: ${currentlyPlaying.artist}`;
|
|
||||||
|
|
||||||
if (currentlyPlaying.playlist) {
|
|
||||||
footer += ` (${currentlyPlaying.playlist.title})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
embed.setFooter({text: footer});
|
|
||||||
|
|
||||||
const queuePageBegin = (queuePage - 1) * PAGE_SIZE;
|
|
||||||
const queuePageEnd = queuePageBegin + PAGE_SIZE;
|
|
||||||
|
|
||||||
player.getQueue().slice(queuePageBegin, queuePageEnd).forEach((song, i) => {
|
|
||||||
embed.addField(`${(i + 1 + queuePageBegin).toString()}/${queueSize.toString()}`, song.title, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
embed.addField('Page', `${queuePage} out of ${maxQueuePage}`, false);
|
|
||||||
|
|
||||||
await msg.channel.send({embeds: [embed]});
|
await msg.channel.send({embeds: [embed]});
|
||||||
} else {
|
|
||||||
await msg.channel.send('queue empty');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import PlayerManager from '../managers/player.js';
|
||||||
import Command from '.';
|
import Command from '.';
|
||||||
import LoadingMessage from '../utils/loading-message.js';
|
import LoadingMessage from '../utils/loading-message.js';
|
||||||
import errorMsg from '../utils/error-msg.js';
|
import errorMsg from '../utils/error-msg.js';
|
||||||
|
import {buildPlayingMessageEmbed} from '../utils/build-embed.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class implements Command {
|
export default class implements Command {
|
||||||
|
@ -39,10 +40,21 @@ export default class implements Command {
|
||||||
try {
|
try {
|
||||||
await loader.start();
|
await loader.start();
|
||||||
await player.forward(numToSkip);
|
await player.forward(numToSkip);
|
||||||
|
|
||||||
await loader.stop('keep \'er movin\'');
|
|
||||||
} catch (_: unknown) {
|
} catch (_: unknown) {
|
||||||
await loader.stop(errorMsg('no song to skip to'));
|
await loader.stop(errorMsg('no song to skip to'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
loader.stop('keep \'er movin\''),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (player.getCurrent()) {
|
||||||
|
promises.push(msg.channel.send({
|
||||||
|
embeds: [buildPlayingMessageEmbed(player)],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {inject, injectable} from 'inversify';
|
||||||
import PlayerManager from '../managers/player.js';
|
import PlayerManager from '../managers/player.js';
|
||||||
import errorMsg from '../utils/error-msg.js';
|
import errorMsg from '../utils/error-msg.js';
|
||||||
import Command from '.';
|
import Command from '.';
|
||||||
|
import {buildPlayingMessageEmbed} from '../utils/build-embed.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class implements Command {
|
export default class implements Command {
|
||||||
|
@ -26,10 +27,14 @@ export default class implements Command {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await player.back();
|
await player.back();
|
||||||
|
|
||||||
await msg.channel.send('back \'er up\'');
|
|
||||||
} catch (_: unknown) {
|
} catch (_: unknown) {
|
||||||
await msg.channel.send(errorMsg('no song to go back to'));
|
await msg.channel.send(errorMsg('no song to go back to'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await msg.channel.send({
|
||||||
|
content: 'back \'er up\'',
|
||||||
|
embeds: [buildPlayingMessageEmbed(player)],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ 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';
|
||||||
|
|
||||||
type QueuedSongWithoutChannel = Except<QueuedSong, 'addedInChannelId'>;
|
type SongMetadata = Except<QueuedSong, 'addedInChannelId' | 'requestedBy'>;
|
||||||
|
|
||||||
const ONE_HOUR_IN_SECONDS = 60 * 60;
|
const ONE_HOUR_IN_SECONDS = 60 * 60;
|
||||||
const ONE_MINUTE_IN_SECONDS = 1 * 60;
|
const ONE_MINUTE_IN_SECONDS = 1 * 60;
|
||||||
|
@ -42,7 +42,7 @@ export default class {
|
||||||
this.ytsrQueue = new PQueue({concurrency: 4});
|
this.ytsrQueue = new PQueue({concurrency: 4});
|
||||||
}
|
}
|
||||||
|
|
||||||
async youtubeVideoSearch(query: string): Promise<QueuedSongWithoutChannel> {
|
async youtubeVideoSearch(query: string): Promise<SongMetadata> {
|
||||||
const {items} = await this.ytsrQueue.add(async () => this.cache.wrap(
|
const {items} = await this.ytsrQueue.add(async () => this.cache.wrap(
|
||||||
ytsr,
|
ytsr,
|
||||||
query,
|
query,
|
||||||
|
@ -70,7 +70,7 @@ export default class {
|
||||||
return this.youtubeVideo(firstVideo.id);
|
return this.youtubeVideo(firstVideo.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async youtubeVideo(url: string): Promise<QueuedSongWithoutChannel> {
|
async youtubeVideo(url: string): Promise<SongMetadata> {
|
||||||
const videoDetails = await this.cache.wrap(
|
const videoDetails = await this.cache.wrap(
|
||||||
this.youtube.videos.get,
|
this.youtube.videos.get,
|
||||||
cleanUrl(url),
|
cleanUrl(url),
|
||||||
|
@ -86,10 +86,11 @@ export default class {
|
||||||
url: videoDetails.id,
|
url: videoDetails.id,
|
||||||
playlist: null,
|
playlist: null,
|
||||||
isLive: videoDetails.snippet.liveBroadcastContent === 'live',
|
isLive: videoDetails.snippet.liveBroadcastContent === 'live',
|
||||||
|
thumbnailUrl: videoDetails.snippet.thumbnails.medium.url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async youtubePlaylist(listId: string): Promise<QueuedSongWithoutChannel[]> {
|
async youtubePlaylist(listId: string): Promise<SongMetadata[]> {
|
||||||
// YouTube playlist
|
// YouTube playlist
|
||||||
const playlist = await this.cache.wrap(
|
const playlist = await this.cache.wrap(
|
||||||
this.youtube.playlists.get,
|
this.youtube.playlists.get,
|
||||||
|
@ -158,7 +159,7 @@ export default class {
|
||||||
|
|
||||||
const queuedPlaylist = {title: playlist.snippet.title, source: playlist.id};
|
const queuedPlaylist = {title: playlist.snippet.title, source: playlist.id};
|
||||||
|
|
||||||
const songsToReturn: QueuedSongWithoutChannel[] = [];
|
const songsToReturn: SongMetadata[] = [];
|
||||||
|
|
||||||
for (const video of playlistVideos) {
|
for (const video of playlistVideos) {
|
||||||
try {
|
try {
|
||||||
|
@ -171,6 +172,7 @@ export default class {
|
||||||
url: video.contentDetails.videoId,
|
url: video.contentDetails.videoId,
|
||||||
playlist: queuedPlaylist,
|
playlist: queuedPlaylist,
|
||||||
isLive: false,
|
isLive: false,
|
||||||
|
thumbnailUrl: video.snippet.thumbnails.medium.url,
|
||||||
});
|
});
|
||||||
} catch (_: unknown) {
|
} catch (_: unknown) {
|
||||||
// Private and deleted videos are sometimes in playlists, duration of these is not returned and they should not be added to the queue.
|
// Private and deleted videos are sometimes in playlists, duration of these is not returned and they should not be added to the queue.
|
||||||
|
@ -180,7 +182,7 @@ export default class {
|
||||||
return songsToReturn;
|
return songsToReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
async spotifySource(url: string, playlistLimit: number): Promise<[QueuedSongWithoutChannel[], number, number]> {
|
async spotifySource(url: string, playlistLimit: number): Promise<[SongMetadata[], number, number]> {
|
||||||
const parsed = spotifyURI.parse(url);
|
const parsed = spotifyURI.parse(url);
|
||||||
|
|
||||||
let tracks: SpotifyApi.TrackObjectSimplified[] = [];
|
let tracks: SpotifyApi.TrackObjectSimplified[] = [];
|
||||||
|
@ -258,7 +260,7 @@ export default class {
|
||||||
let nSongsNotFound = 0;
|
let nSongsNotFound = 0;
|
||||||
|
|
||||||
// Get rid of null values
|
// Get rid of null values
|
||||||
songs = songs.reduce((accum: QueuedSongWithoutChannel[], song) => {
|
songs = songs.reduce((accum: SongMetadata[], song) => {
|
||||||
if (song) {
|
if (song) {
|
||||||
accum.push(song);
|
accum.push(song);
|
||||||
} else {
|
} else {
|
||||||
|
@ -268,10 +270,10 @@ export default class {
|
||||||
return accum;
|
return accum;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return [songs as QueuedSongWithoutChannel[], nSongsNotFound, originalNSongs];
|
return [songs as SongMetadata[], nSongsNotFound, originalNSongs];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async spotifyToYouTube(track: SpotifyApi.TrackObjectSimplified, _: QueuedPlaylist | null): Promise<QueuedSongWithoutChannel> {
|
private async spotifyToYouTube(track: SpotifyApi.TrackObjectSimplified, _: QueuedPlaylist | null): Promise<SongMetadata> {
|
||||||
return this.youtubeVideoSearch(`"${track.name}" "${track.artists[0].name}"`);
|
return this.youtubeVideoSearch(`"${track.name}" "${track.artists[0].name}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ export default class {
|
||||||
playlist: null,
|
playlist: null,
|
||||||
isLive: false,
|
isLive: false,
|
||||||
addedInChannelId: msg.channel.id,
|
addedInChannelId: msg.channel.id,
|
||||||
|
thumbnailUrl: null,
|
||||||
|
requestedBy: msg.author.id,
|
||||||
}, 8, 10),
|
}, 8, 10),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -49,6 +51,8 @@ export default class {
|
||||||
playlist: null,
|
playlist: null,
|
||||||
isLive: false,
|
isLive: false,
|
||||||
addedInChannelId: msg.channel.id,
|
addedInChannelId: msg.channel.id,
|
||||||
|
thumbnailUrl: null,
|
||||||
|
requestedBy: msg.author.id,
|
||||||
}, 358, 5.5),
|
}, 358, 5.5),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -66,6 +70,8 @@ export default class {
|
||||||
playlist: null,
|
playlist: null,
|
||||||
isLive: false,
|
isLive: false,
|
||||||
addedInChannelId: msg.channel.id,
|
addedInChannelId: msg.channel.id,
|
||||||
|
thumbnailUrl: null,
|
||||||
|
requestedBy: msg.author.id,
|
||||||
}, 50, 13),
|
}, 50, 13),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ export interface QueuedSong {
|
||||||
playlist: QueuedPlaylist | null;
|
playlist: QueuedPlaylist | null;
|
||||||
isLive: boolean;
|
isLive: boolean;
|
||||||
addedInChannelId: Snowflake;
|
addedInChannelId: Snowflake;
|
||||||
|
thumbnailUrl: string | null;
|
||||||
|
requestedBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum STATUS {
|
export enum STATUS {
|
||||||
|
|
127
src/utils/build-embed.ts
Normal file
127
src/utils/build-embed.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import getYouTubeID from 'get-youtube-id';
|
||||||
|
import {MessageEmbed} from 'discord.js';
|
||||||
|
import Player, {QueuedSong, STATUS} from '../services/player.js';
|
||||||
|
import getProgressBar from './get-progress-bar.js';
|
||||||
|
import {prettyTime} from './time.js';
|
||||||
|
import {truncate} from './string.js';
|
||||||
|
|
||||||
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
const getMaxSongTitleLength = (title: string) => {
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const nonASCII = /[^\x00-\x7F]+/;
|
||||||
|
return nonASCII.test(title) ? 28 : 48;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSongTitle = ({title, url}: QueuedSong, shouldTruncate = false) => {
|
||||||
|
const cleanSongTitle = title.replace(/\[.*\]/, '').trim();
|
||||||
|
|
||||||
|
const songTitle = shouldTruncate ? truncate(cleanSongTitle, getMaxSongTitleLength(cleanSongTitle)) : cleanSongTitle;
|
||||||
|
const youtubeId = url.length === 11 ? url : getYouTubeID(url) ?? '';
|
||||||
|
|
||||||
|
return `[${songTitle}](https://www.youtube.com/watch?v=${youtubeId})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQueueInfo = (player: Player) => {
|
||||||
|
const queueSize = player.queueSize();
|
||||||
|
if (queueSize === 0) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return queueSize === 1 ? '1 song' : `${queueSize} songs`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPlayerUI = (player: Player) => {
|
||||||
|
const song = player.getCurrent();
|
||||||
|
|
||||||
|
if (!song) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = player.getPosition();
|
||||||
|
const button = player.status === STATUS.PLAYING ? '⏹️' : '▶️';
|
||||||
|
const progressBar = getProgressBar(15, position / song.length);
|
||||||
|
const elapsedTime = `${prettyTime(position)}/${song.isLive ? 'live' : prettyTime(song.length)}`;
|
||||||
|
|
||||||
|
return `${button} ${progressBar} \`[${elapsedTime}]\` 🔉`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildPlayingMessageEmbed = (player: Player): MessageEmbed => {
|
||||||
|
const currentlyPlaying = player.getCurrent();
|
||||||
|
|
||||||
|
if (!currentlyPlaying) {
|
||||||
|
throw new Error('No playing song found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {artist, thumbnailUrl, requestedBy} = currentlyPlaying;
|
||||||
|
const message = new MessageEmbed();
|
||||||
|
|
||||||
|
message
|
||||||
|
.setColor('DARK_GREEN')
|
||||||
|
.setTitle('Now Playing')
|
||||||
|
.setDescription(`
|
||||||
|
**${getSongTitle(currentlyPlaying)}**
|
||||||
|
Requested by: <@${requestedBy}>\n
|
||||||
|
${getPlayerUI(player)}
|
||||||
|
`)
|
||||||
|
.setFooter({text: `Source: ${artist}`});
|
||||||
|
|
||||||
|
if (thumbnailUrl) {
|
||||||
|
message.setThumbnail(thumbnailUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildQueueEmbed = (player: Player, page: number): MessageEmbed => {
|
||||||
|
const currentlyPlaying = player.getCurrent();
|
||||||
|
|
||||||
|
if (!currentlyPlaying) {
|
||||||
|
throw new Error('queue is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
const queueSize = player.queueSize();
|
||||||
|
const maxQueuePage = Math.ceil((queueSize + 1) / PAGE_SIZE);
|
||||||
|
|
||||||
|
if (page > maxQueuePage) {
|
||||||
|
throw new Error('the queue isn\'t that big');
|
||||||
|
}
|
||||||
|
|
||||||
|
const queuePageBegin = (page - 1) * PAGE_SIZE;
|
||||||
|
const queuePageEnd = queuePageBegin + PAGE_SIZE;
|
||||||
|
const queuedSongs = player
|
||||||
|
.getQueue()
|
||||||
|
.slice(queuePageBegin, queuePageEnd)
|
||||||
|
.map((song, index) => `\`${index + 1 + queuePageBegin}.\` ${getSongTitle(song, true)} \`[${prettyTime(song.length)}]\``)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const {artist, thumbnailUrl, playlist, requestedBy} = currentlyPlaying;
|
||||||
|
const playlistTitle = playlist ? `(${playlist.title})` : '';
|
||||||
|
const totalLength = player.getQueue().reduce((accumulator, current) => accumulator + current.length, 0);
|
||||||
|
|
||||||
|
const message = new MessageEmbed();
|
||||||
|
|
||||||
|
let description = `**${getSongTitle(currentlyPlaying)}**\n`;
|
||||||
|
description += `Requested by: <@${requestedBy}>\n\n`;
|
||||||
|
description += `${getPlayerUI(player)}\n\n`;
|
||||||
|
|
||||||
|
if (player.getQueue().length > 0) {
|
||||||
|
description += '**Up next:**\n';
|
||||||
|
description += queuedSongs;
|
||||||
|
}
|
||||||
|
|
||||||
|
message
|
||||||
|
.setTitle(player.status === STATUS.PLAYING ? 'Now Playing' : 'Queued songs')
|
||||||
|
.setColor(player.status === STATUS.PLAYING ? 'DARK_GREEN' : 'NOT_QUITE_BLACK')
|
||||||
|
.setDescription(description)
|
||||||
|
.addField('In queue', getQueueInfo(player), true)
|
||||||
|
.addField('Total length', `${totalLength > 0 ? prettyTime(totalLength) : '-'}`, true)
|
||||||
|
.addField('Page', `${page} out of ${maxQueuePage}`, true)
|
||||||
|
.setFooter({text: `Source: ${artist} ${playlistTitle}`});
|
||||||
|
|
||||||
|
if (thumbnailUrl) {
|
||||||
|
message.setThumbnail(thumbnailUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
};
|
2
src/utils/string.ts
Normal file
2
src/utils/string.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const truncate = (text: string, maxLength = 50) =>
|
||||||
|
text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text;
|
19
yarn.lock
19
yarn.lock
|
@ -3740,7 +3740,16 @@ youtube.ts@^0.2.5:
|
||||||
axios "^0.19.0"
|
axios "^0.19.0"
|
||||||
ytdl-core "^4.9.1"
|
ytdl-core "^4.9.1"
|
||||||
|
|
||||||
ytdl-core@^4.9.1, ytdl-core@^4.9.2:
|
ytdl-core@^4.10.0:
|
||||||
|
version "4.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-4.10.0.tgz#0835cb411677684539fac2bcc10553f6f58db3e1"
|
||||||
|
integrity sha512-RCCoSVTmMeBPH5NFR1fh3nkDU9okvWM0ZdN6plw6I5+vBBZVUEpOt8vjbSgprLRMmGUsmrQZJhvG1CHOat4mLA==
|
||||||
|
dependencies:
|
||||||
|
m3u8stream "^0.8.4"
|
||||||
|
miniget "^4.0.0"
|
||||||
|
sax "^1.1.3"
|
||||||
|
|
||||||
|
ytdl-core@^4.9.1:
|
||||||
version "4.9.2"
|
version "4.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-4.9.2.tgz#c2d1ec44ee3cabff35e5843c6831755e69ffacf0"
|
resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-4.9.2.tgz#c2d1ec44ee3cabff35e5843c6831755e69ffacf0"
|
||||||
integrity sha512-aTlsvsN++03MuOtyVD4DRF9Z/9UAeeuiNbjs+LjQBAiw4Hrdp48T3U9vAmRPyvREzupraY8pqRoBfKGqpq+eHA==
|
integrity sha512-aTlsvsN++03MuOtyVD4DRF9Z/9UAeeuiNbjs+LjQBAiw4Hrdp48T3U9vAmRPyvREzupraY8pqRoBfKGqpq+eHA==
|
||||||
|
@ -3749,10 +3758,10 @@ ytdl-core@^4.9.1, ytdl-core@^4.9.2:
|
||||||
miniget "^4.0.0"
|
miniget "^4.0.0"
|
||||||
sax "^1.1.3"
|
sax "^1.1.3"
|
||||||
|
|
||||||
ytsr@^3.5.3:
|
ytsr@^3.6.0:
|
||||||
version "3.5.3"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/ytsr/-/ytsr-3.5.3.tgz#88e8e2df11ce53c28b456b5510272495cb42ac3a"
|
resolved "https://registry.yarnpkg.com/ytsr/-/ytsr-3.6.0.tgz#bc55e8957dcc293e49e18cc3b3e6d2890d15a15e"
|
||||||
integrity sha512-BEyIKbQULmk27hiVUQ1cBszAqP8roPBOQTWPZpBioKxjSZBeicfgF2qPIQoY7koodQwRuo1DmCFz3DyrXjADxg==
|
integrity sha512-3fN8lxL+JHtp2xEZoAK3AeTjNm5WB4MH6n2OxHNxP06xQtuO5khbLwh6IJGiZRNi/v3de+jYYbctp2pUqNT/Qw==
|
||||||
dependencies:
|
dependencies:
|
||||||
miniget "^4.2.1"
|
miniget "^4.2.1"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue