2017-05-14 19:00:35 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Discord;
|
|
|
|
|
using NadekoBot.Extensions;
|
|
|
|
|
using NadekoBot.Modules;
|
2017-05-22 23:59:31 +00:00
|
|
|
|
using NadekoBot.Services.Impl;
|
2017-05-14 19:00:35 +00:00
|
|
|
|
|
|
|
|
|
namespace NadekoBot.Services.Music
|
|
|
|
|
{
|
|
|
|
|
public class MusicService
|
|
|
|
|
{
|
|
|
|
|
private readonly IGoogleApiService _google;
|
2017-05-22 23:59:31 +00:00
|
|
|
|
private readonly NadekoStrings _strings;
|
|
|
|
|
private readonly ILocalization _localization;
|
|
|
|
|
private GoogleApiService google;
|
|
|
|
|
|
2017-05-14 19:00:35 +00:00
|
|
|
|
public ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
|
|
|
|
|
|
2017-05-22 23:59:31 +00:00
|
|
|
|
public MusicService(IGoogleApiService google, NadekoStrings strings, ILocalization localization)
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
|
|
|
|
_google = google;
|
2017-05-22 23:59:31 +00:00
|
|
|
|
_strings = strings;
|
|
|
|
|
_localization = localization;
|
2017-05-14 19:00:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public MusicPlayer GetPlayer(ulong guildId)
|
|
|
|
|
{
|
|
|
|
|
MusicPlayers.TryGetValue(guildId, out var player);
|
|
|
|
|
return player;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public MusicPlayer GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh)
|
|
|
|
|
{
|
|
|
|
|
string GetText(string text, params object[] replacements) =>
|
2017-05-22 23:59:31 +00:00
|
|
|
|
_strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
|
2017-05-14 19:00:35 +00:00
|
|
|
|
|
|
|
|
|
return MusicPlayers.GetOrAdd(guildId, server =>
|
|
|
|
|
{
|
|
|
|
|
float vol;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
|
|
|
|
|
using (var uow = DbHandler.UnitOfWork())
|
|
|
|
|
{
|
|
|
|
|
//todo move to cached variable
|
|
|
|
|
vol = uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume;
|
|
|
|
|
}
|
2017-05-22 23:59:31 +00:00
|
|
|
|
var mp = new MusicPlayer(voiceCh, textCh, vol, _google);
|
2017-05-14 19:00:35 +00:00
|
|
|
|
IUserMessage playingMessage = null;
|
|
|
|
|
IUserMessage lastFinishedMessage = null;
|
|
|
|
|
mp.OnCompleted += async (s, song) =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
lastFinishedMessage?.DeleteAfter(0);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
lastFinishedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
|
|
|
|
.WithAuthor(eab => eab.WithName(GetText("finished_song")).WithMusicIcon())
|
|
|
|
|
.WithDescription(song.PrettyName)
|
|
|
|
|
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.ProviderType == MusicType.Normal)
|
|
|
|
|
{
|
2017-05-22 23:59:31 +00:00
|
|
|
|
var relatedVideos = (await _google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList();
|
2017-05-14 19:00:35 +00:00
|
|
|
|
if (relatedVideos.Count > 0)
|
|
|
|
|
await QueueSong(await textCh.Guild.GetCurrentUserAsync(),
|
|
|
|
|
textCh,
|
|
|
|
|
voiceCh,
|
|
|
|
|
relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)],
|
|
|
|
|
true).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mp.OnStarted += async (player, song) =>
|
|
|
|
|
{
|
|
|
|
|
try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); }
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
var sender = player;
|
|
|
|
|
if (sender == null)
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
playingMessage?.DeleteAfter(0);
|
|
|
|
|
|
|
|
|
|
playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
|
|
|
|
.WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon())
|
|
|
|
|
.WithDescription(song.PrettyName)
|
|
|
|
|
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
mp.OnPauseChanged += async (paused) =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
IUserMessage msg;
|
|
|
|
|
if (paused)
|
|
|
|
|
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("paused")).ConfigureAwait(false);
|
|
|
|
|
else
|
|
|
|
|
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("resumed")).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
msg?.DeleteAfter(10);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mp.SongRemoved += async (song, index) =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var embed = new EmbedBuilder()
|
|
|
|
|
.WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index + 1)).WithMusicIcon())
|
|
|
|
|
.WithDescription(song.PrettyName)
|
|
|
|
|
.WithFooter(ef => ef.WithText(song.PrettyInfo))
|
|
|
|
|
.WithErrorColor();
|
|
|
|
|
|
|
|
|
|
await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return mp;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
|
|
|
|
|
{
|
|
|
|
|
string GetText(string text, params object[] replacements) =>
|
2017-05-22 23:59:31 +00:00
|
|
|
|
_strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
|
2017-05-14 19:00:35 +00:00
|
|
|
|
|
|
|
|
|
if (voiceCh == null || voiceCh.Guild != textCh.Guild)
|
|
|
|
|
{
|
|
|
|
|
if (!silent)
|
|
|
|
|
await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false);
|
|
|
|
|
throw new ArgumentNullException(nameof(voiceCh));
|
|
|
|
|
}
|
|
|
|
|
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
|
|
|
|
|
throw new ArgumentException("Invalid song query.", nameof(query));
|
|
|
|
|
|
|
|
|
|
var musicPlayer = GetOrCreatePlayer(textCh.Guild.Id, voiceCh, textCh);
|
|
|
|
|
Song resolvedSong;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
musicPlayer.ThrowIfQueueFull();
|
|
|
|
|
resolvedSong = await SongHandler.ResolveSong(query, musicType).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
if (resolvedSong == null)
|
|
|
|
|
throw new SongNotFoundException();
|
|
|
|
|
|
|
|
|
|
musicPlayer.AddSong(resolvedSong, queuer.Username);
|
|
|
|
|
}
|
|
|
|
|
catch (PlaylistFullException)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize));
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
if (!silent)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
//var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
|
|
|
|
|
var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
|
|
|
|
|
.WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon())
|
|
|
|
|
.WithDescription($"{resolvedSong.PrettyName}\n{GetText("queue")} ")
|
|
|
|
|
.WithThumbnailUrl(resolvedSong.Thumbnail)
|
|
|
|
|
.WithFooter(ef => ef.WithText(resolvedSong.PrettyProvider)))
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
queuedMessage?.DeleteAfter(10);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
} // if queued message sending fails, don't attempt to delete it
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void DestroyPlayer(ulong id)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|