mirror of
https://github.com/BluemediaDev/muse.git
synced 2024-11-26 20:02:06 +01:00
Correctly skip song if unavailable
Also lets user know in text channel that song is unavailable after skipping. Fixes #324
This commit is contained in:
parent
81bbdb971d
commit
9a2ef876d3
|
@ -14,7 +14,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"lint": "eslint 'src/**/*.ts'",
|
||||
"lint-fix": "eslint 'src/**/*.ts' --fix",
|
||||
"lint:fix": "eslint 'src/**/*.ts' --fix",
|
||||
"clean": "rm -rf dist dts",
|
||||
"test": "npm run lint",
|
||||
"build": "tsc",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {TextChannel, Message} from 'discord.js';
|
||||
import {URL} from 'url';
|
||||
import {Except} from 'type-fest';
|
||||
import {TYPES} from '../types';
|
||||
import {inject, injectable} from 'inversify';
|
||||
import {QueuedSong, STATUS} from '../services/player';
|
||||
|
@ -68,7 +69,7 @@ export default class implements Command {
|
|||
|
||||
const addToFrontOfQueue = args[args.length - 1] === 'i' || args[args.length - 1] === 'immediate';
|
||||
|
||||
const newSongs: QueuedSong[] = [];
|
||||
const newSongs: Array<Except<QueuedSong, 'addedInChannelId'>> = [];
|
||||
let extraMsg = '';
|
||||
|
||||
// Test if it's a complete URL
|
||||
|
@ -133,7 +134,7 @@ export default class implements Command {
|
|||
return;
|
||||
}
|
||||
|
||||
newSongs.forEach(song => player.add(song, {immediate: addToFrontOfQueue}));
|
||||
newSongs.forEach(song => player.add({...song, addedInChannelId: msg.channel.id}, {immediate: addToFrontOfQueue}));
|
||||
|
||||
const firstSong = newSongs[0];
|
||||
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import {inject, injectable} from 'inversify';
|
||||
import {TYPES} from '../types';
|
||||
import Player from '../services/player';
|
||||
import {Client} from 'discord.js';
|
||||
|
||||
@injectable()
|
||||
export default class {
|
||||
private readonly guildPlayers: Map<string, Player>;
|
||||
private readonly cacheDir: string;
|
||||
private readonly discordClient: Client;
|
||||
|
||||
constructor(@inject(TYPES.Config.CACHE_DIR) cacheDir: string) {
|
||||
constructor(@inject(TYPES.Config.CACHE_DIR) cacheDir: string, @inject(TYPES.Client) client: Client) {
|
||||
this.guildPlayers = new Map();
|
||||
this.cacheDir = cacheDir;
|
||||
this.discordClient = client;
|
||||
}
|
||||
|
||||
get(guildId: string): Player {
|
||||
let player = this.guildPlayers.get(guildId);
|
||||
|
||||
if (!player) {
|
||||
player = new Player(this.cacheDir);
|
||||
player = new Player(this.cacheDir, this.discordClient);
|
||||
|
||||
this.guildPlayers.set(guildId, player);
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@ import Spotify from 'spotify-web-api-node';
|
|||
import YouTube, {YoutubePlaylistItem} from 'youtube.ts';
|
||||
import pLimit from 'p-limit';
|
||||
import shuffle from 'array-shuffle';
|
||||
import {Except} from 'type-fest';
|
||||
import {QueuedSong, QueuedPlaylist} from '../services/player';
|
||||
import {TYPES} from '../types';
|
||||
import {cleanUrl} from '../utils/url';
|
||||
|
||||
type QueuedSongWithoutChannel = Except<QueuedSong, 'addedInChannelId'>;
|
||||
|
||||
@injectable()
|
||||
export default class {
|
||||
private readonly youtube: YouTube;
|
||||
|
@ -23,7 +26,7 @@ export default class {
|
|||
this.spotify = spotify;
|
||||
}
|
||||
|
||||
async youtubeVideoSearch(query: string): Promise<QueuedSong|null> {
|
||||
async youtubeVideoSearch(query: string): Promise<QueuedSongWithoutChannel|null> {
|
||||
try {
|
||||
const {items: [video]} = await this.youtube.videos.search({q: query, maxResults: 1, type: 'video'});
|
||||
|
||||
|
@ -33,7 +36,7 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
async youtubeVideo(url: string): Promise<QueuedSong|null> {
|
||||
async youtubeVideo(url: string): Promise<QueuedSongWithoutChannel|null> {
|
||||
try {
|
||||
const videoDetails = await this.youtube.videos.get(cleanUrl(url));
|
||||
|
||||
|
@ -50,7 +53,7 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
async youtubePlaylist(listId: string): Promise<QueuedSong[]> {
|
||||
async youtubePlaylist(listId: string): Promise<QueuedSongWithoutChannel[]> {
|
||||
// YouTube playlist
|
||||
const playlist = await this.youtube.playlists.get(listId);
|
||||
|
||||
|
@ -93,7 +96,7 @@ export default class {
|
|||
|
||||
const queuedPlaylist = {title: playlist.snippet.title, source: playlist.id};
|
||||
|
||||
const songsToReturn: QueuedSong[] = [];
|
||||
const songsToReturn: QueuedSongWithoutChannel[] = [];
|
||||
|
||||
for (let video of playlistVideos) {
|
||||
try {
|
||||
|
@ -115,7 +118,7 @@ export default class {
|
|||
return songsToReturn;
|
||||
}
|
||||
|
||||
async spotifySource(url: string): Promise<[QueuedSong[], number, number]> {
|
||||
async spotifySource(url: string): Promise<[QueuedSongWithoutChannel[], number, number]> {
|
||||
const parsed = spotifyURI.parse(url);
|
||||
|
||||
let tracks: SpotifyApi.TrackObjectSimplified[] = [];
|
||||
|
@ -195,7 +198,7 @@ export default class {
|
|||
let nSongsNotFound = 0;
|
||||
|
||||
// Get rid of null values
|
||||
songs = songs.reduce((accum: QueuedSong[], song) => {
|
||||
songs = songs.reduce((accum: QueuedSongWithoutChannel[], song) => {
|
||||
if (song) {
|
||||
accum.push(song);
|
||||
} else {
|
||||
|
@ -205,10 +208,10 @@ export default class {
|
|||
return accum;
|
||||
}, []);
|
||||
|
||||
return [songs as QueuedSong[], nSongsNotFound, originalNSongs];
|
||||
return [songs as QueuedSongWithoutChannel[], nSongsNotFound, originalNSongs];
|
||||
}
|
||||
|
||||
private async spotifyToYouTube(track: SpotifyApi.TrackObjectSimplified, _: QueuedPlaylist | null): Promise<QueuedSong | null> {
|
||||
private async spotifyToYouTube(track: SpotifyApi.TrackObjectSimplified, _: QueuedPlaylist | null): Promise<QueuedSongWithoutChannel | null> {
|
||||
try {
|
||||
const {items} = await this.youtube.videos.search({q: `"${track.name}" "${track.artists[0].name}"`, maxResults: 10});
|
||||
const videoResult = items[0]; // Items.find(item => item.type === 'video');
|
||||
|
|
|
@ -24,7 +24,15 @@ export default class {
|
|||
if (msg.content.toLowerCase().includes('packers')) {
|
||||
await Promise.all([
|
||||
msg.channel.send('GO PACKERS GO!!!'),
|
||||
this.playClip(msg.guild!, msg.member!, {title: 'GO PACKERS!', artist: 'Unknown', url: 'https://www.youtube.com/watch?v=qkdtID7mY3E', length: 204, playlist: null, isLive: false}, 8, 10)
|
||||
this.playClip(msg.guild!, msg.member!, {
|
||||
title: 'GO PACKERS!',
|
||||
artist: 'Unknown',
|
||||
url: 'https://www.youtube.com/watch?v=qkdtID7mY3E',
|
||||
length: 204,
|
||||
playlist: null,
|
||||
isLive: false,
|
||||
addedInChannelId: msg.channel.id
|
||||
}, 8, 10)
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
@ -33,7 +41,15 @@ export default class {
|
|||
if (msg.content.toLowerCase().includes('bears')) {
|
||||
await Promise.all([
|
||||
msg.channel.send('F*** THE BEARS'),
|
||||
this.playClip(msg.guild!, msg.member!, {title: 'GO PACKERS!', artist: 'Charlie Berens', url: 'https://www.youtube.com/watch?v=UaqlE9Pyy_Q', length: 385, playlist: null, isLive: false}, 358, 5.5)
|
||||
this.playClip(msg.guild!, msg.member!, {
|
||||
title: 'GO PACKERS!',
|
||||
artist: 'Charlie Berens',
|
||||
url: 'https://www.youtube.com/watch?v=UaqlE9Pyy_Q',
|
||||
length: 385,
|
||||
playlist: null,
|
||||
isLive: false,
|
||||
addedInChannelId: msg.channel.id
|
||||
}, 358, 5.5)
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
@ -42,7 +58,15 @@ export default class {
|
|||
if (msg.content.toLowerCase().includes('bitconnect')) {
|
||||
await Promise.all([
|
||||
msg.channel.send('🌊 🌊 🌊 🌊'),
|
||||
this.playClip(msg.guild!, msg.member!, {title: 'BITCONNEEECCT', artist: 'Carlos Matos', url: 'https://www.youtube.com/watch?v=lCcwn6bGUtU', length: 227, playlist: null, isLive: false}, 50, 13)
|
||||
this.playClip(msg.guild!, msg.member!, {
|
||||
title: 'BITCONNEEECCT',
|
||||
artist: 'Carlos Matos',
|
||||
url: 'https://www.youtube.com/watch?v=lCcwn6bGUtU',
|
||||
length: 227,
|
||||
playlist: null,
|
||||
isLive: false,
|
||||
addedInChannelId: msg.channel.id
|
||||
}, 50, 13)
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {VoiceConnection, VoiceChannel, StreamDispatcher} from 'discord.js';
|
||||
import {VoiceConnection, VoiceChannel, StreamDispatcher, Snowflake, Client, TextChannel} from 'discord.js';
|
||||
import {promises as fs, createWriteStream} from 'fs';
|
||||
import {Readable, PassThrough} from 'stream';
|
||||
import path from 'path';
|
||||
|
@ -7,6 +7,7 @@ import ytdl from 'ytdl-core';
|
|||
import {WriteStream} from 'fs-capacitor';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import shuffle from 'array-shuffle';
|
||||
import errorMsg from '../utils/error-msg';
|
||||
|
||||
export interface QueuedPlaylist {
|
||||
title: string;
|
||||
|
@ -20,6 +21,7 @@ export interface QueuedSong {
|
|||
length: number;
|
||||
playlist: QueuedPlaylist | null;
|
||||
isLive: boolean;
|
||||
addedInChannelId: Snowflake;
|
||||
}
|
||||
|
||||
export enum STATUS {
|
||||
|
@ -40,8 +42,11 @@ export default class {
|
|||
|
||||
private positionInSeconds = 0;
|
||||
|
||||
constructor(cacheDir: string) {
|
||||
private readonly discordClient: Client;
|
||||
|
||||
constructor(cacheDir: string, client: Client) {
|
||||
this.cacheDir = cacheDir;
|
||||
this.discordClient = client;
|
||||
}
|
||||
|
||||
async connect(channel: VoiceChannel): Promise<void> {
|
||||
|
@ -142,7 +147,18 @@ export default class {
|
|||
this.lastSongURL = currentSong.url;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
this.removeCurrent();
|
||||
const currentSong = this.getCurrent();
|
||||
await this.forward(1);
|
||||
|
||||
if ((error as {statusCode: number}).statusCode === 410 && currentSong) {
|
||||
const channelId = currentSong.addedInChannelId;
|
||||
|
||||
if (channelId) {
|
||||
await (this.discordClient.channels.cache.get(channelId) as TextChannel).send(errorMsg(`${currentSong.title} is unavailable`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue