From 727da69c819a625b83b8b678e221ac2fcc639f4b Mon Sep 17 00:00:00 2001 From: Kwoth Date: Thu, 22 Dec 2016 04:49:33 +0100 Subject: [PATCH] Some cleanup and many music improvements --- .../Modules/ClashOfClans/ClashOfClans.cs | 42 +- .../CustomReactions/CustomReactions.cs | 6 +- .../Games/Commands/Hangman/HangmanGame.cs | 2 +- .../Games/Commands/Trivia/TriviaGame.cs | 2 +- src/NadekoBot/Modules/Games/Games.cs | 2 +- src/NadekoBot/Modules/Help/Help.cs | 2 +- .../Modules/Music/Classes/MusicControls.cs | 13 +- .../Modules/Music/Classes/MusicExtensions.cs | 15 + src/NadekoBot/Modules/Music/Classes/Song.cs | 315 ++------------- .../Modules/Music/Classes/SongHandler.cs | 212 ++++++++++ src/NadekoBot/Modules/Music/Music.cs | 361 +++++++----------- src/NadekoBot/Modules/Pokemon/Pokemon.cs | 2 +- .../Modules/Searches/Commands/LoLCommands.cs | 2 +- .../Searches/Commands/OMDB/OmdbProvider.cs | 3 +- .../Searches/Commands/OverwatchCommands.cs | 6 +- .../Commands/PokemonSearchCommands.cs | 4 +- .../Modules/Searches/Commands/XkcdCommands.cs | 2 +- src/NadekoBot/Modules/Searches/Searches.cs | 12 +- .../Modules/Utility/Commands/InfoCommands.cs | 6 +- .../Utility/Commands/UnitConversion.cs | 2 +- src/NadekoBot/Modules/Utility/Utility.cs | 2 +- src/NadekoBot/_Extensions/Extensions.cs | 14 + 22 files changed, 475 insertions(+), 552 deletions(-) create mode 100644 src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs create mode 100644 src/NadekoBot/Modules/Music/Classes/SongHandler.cs diff --git a/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs index 81be7144..0546837a 100644 --- a/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs +++ b/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs @@ -11,6 +11,7 @@ using Discord.WebSocket; using NadekoBot.Services.Database.Models; using System.Linq; using NadekoBot.Extensions; +using System.Threading; namespace NadekoBot.Modules.ClashOfClans { @@ -19,6 +20,8 @@ namespace NadekoBot.Modules.ClashOfClans { public static ConcurrentDictionary> ClashWars { get; set; } = new ConcurrentDictionary>(); + private static Timer checkWarTimer { get; } + static ClashOfClans() { using (var uow = DbHandler.UnitOfWork()) @@ -32,13 +35,21 @@ namespace NadekoBot.Modules.ClashOfClans ?.GetTextChannel(cw.ChannelId); return cw; }) - .Where(cw => cw?.Channel != null) + .Where(cw => cw.Channel != null) .GroupBy(cw => cw.GuildId) .ToDictionary(g => g.Key, g => g.ToList())); } - } - public ClashOfClans() : base() - { + + checkWarTimer = new Timer(async _ => + { + foreach (var kvp in ClashWars) + { + foreach (var war in kvp.Value) + { + try { await CheckWar(TimeSpan.FromHours(2), war).ConfigureAwait(false); } catch { } + } + } + }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); } private static async Task CheckWar(TimeSpan callExpire, ClashWar war) @@ -46,12 +57,21 @@ namespace NadekoBot.Modules.ClashOfClans var Bases = war.Bases; for (var i = 0; i < Bases.Count; i++) { - if (Bases[i].CallUser == null) continue; - if (!Bases[i].BaseDestroyed && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire) + var callUser = Bases[i].CallUser; + if (callUser == null) continue; + if ((!Bases[i].BaseDestroyed) && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire) { - Bases[i] = null; - try { await war.Channel.SendErrorAsync($"β—πŸ”°**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false); } catch { } - } + if (Bases[i].Stars != 3) + Bases[i].BaseDestroyed = true; + else + Bases[i] = null; + try + { + SaveWar(war); + await war.Channel.SendErrorAsync($"β—πŸ”°**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false); + } + catch { } + } } } @@ -216,7 +236,7 @@ namespace NadekoBot.Modules.ClashOfClans { var channel = (ITextChannel)umsg.Channel; - var warsInfo = GetWarInfo(umsg,number); + var warsInfo = GetWarInfo(umsg, number); if (warsInfo == null) { await channel.SendErrorAsync("πŸ”° That war does not exist.").ConfigureAwait(false); @@ -359,4 +379,4 @@ namespace NadekoBot.Modules.ClashOfClans } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs index d7c28f91..d68aeab6 100644 --- a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -115,7 +115,7 @@ namespace NadekoBot.Modules.CustomReactions reactions.Add(cr); } - await imsg.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) + await imsg.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithTitle("New Custom Reaction") .WithDescription($"#{cr.Id}") .AddField(efb => efb.WithName("Trigger").WithValue(key)) @@ -225,7 +225,7 @@ namespace NadekoBot.Modules.CustomReactions await imsg.Channel.SendErrorAsync("No custom reaction found with that id.").ConfigureAwait(false); else { - await imsg.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) + await imsg.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithDescription($"#{id}") .AddField(efb => efb.WithName("Trigger").WithValue(found.Trigger)) .AddField(efb => efb.WithName("Response").WithValue(found.Response + "\n```css\n" + found.Response + "```")) @@ -304,7 +304,7 @@ namespace NadekoBot.Modules.CustomReactions await imsg.Channel.EmbedAsync(ReactionStats.OrderByDescending(x => x.Value) .Skip((page - 1)*9) .Take(9) - .Aggregate(new EmbedBuilder().WithColor(NadekoBot.OkColor).WithTitle($"Custom Reaction stats page #{page}"), + .Aggregate(new EmbedBuilder().WithOkColor().WithTitle($"Custom Reaction stats page #{page}"), (agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true))) .Build()) .ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs index 575e8edf..6baa9404 100644 --- a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs @@ -120,7 +120,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman if (Errors >= MaxErrors) await GameChannel.EmbedAsync(embed.WithColor(NadekoBot.ErrorColor).Build()).ConfigureAwait(false); else - await GameChannel.EmbedAsync(embed.WithColor(NadekoBot.OkColor).Build()).ConfigureAwait(false); + await GameChannel.EmbedAsync(embed.WithOkColor().Build()).ConfigureAwait(false); } private Task PotentialGuess(IMessage msg) diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs index 534c5040..7a2f15c5 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -111,7 +111,7 @@ namespace NadekoBot.Modules.Games.Trivia Games.TriviaCommands.RunningTrivias.TryRemove(channel.Guild.Id, out throwaway); try { - await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) + await channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithTitle("Leaderboard") .WithDescription(GetLeaderboard()) .Build(), "Trivia game ended.").ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index 592881d1..8272598a 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -49,7 +49,7 @@ namespace NadekoBot.Modules.Games return; var rng = new NadekoRandom(); - await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) + await channel.EmbedAsync(new EmbedBuilder().WithOkColor() .AddField(efb => efb.WithName("❓ Question").WithValue(question).WithIsInline(false)) .AddField(efb => efb.WithName("🎱 8Ball").WithValue(_8BallResponses.Shuffle().FirstOrDefault()).WithIsInline(false)) .Build()); diff --git a/src/NadekoBot/Modules/Help/Help.cs b/src/NadekoBot/Modules/Help/Help.cs index 2b824111..2f25f31a 100644 --- a/src/NadekoBot/Modules/Help/Help.cs +++ b/src/NadekoBot/Modules/Help/Help.cs @@ -98,7 +98,7 @@ namespace NadekoBot.Modules.Help var embed = new EmbedBuilder() .AddField(fb => fb.WithIndex(1).WithName(str).WithValue($"{ string.Format(com.Summary, com.Module.Prefix)} { GetCommandRequirements(com)}").WithIsInline(true)) .AddField(fb => fb.WithIndex(2).WithName("**Usage**").WithValue($"{string.Format(com.Remarks, com.Module.Prefix)}").WithIsInline(false)) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs index 8555d3a3..5d10a22a 100644 --- a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -41,9 +41,9 @@ namespace NadekoBot.Modules.Music.Classes public float Volume { get; private set; } - public event EventHandler OnCompleted = delegate { }; - public event EventHandler OnStarted = delegate { }; - public event Action OnPauseChanged = delegate { }; + public event Func OnCompleted = delegate { return Task.CompletedTask; }; + public event Func OnStarted = delegate { return Task.CompletedTask; }; + public event Func OnPauseChanged = delegate { return Task.CompletedTask; }; public IVoiceChannel PlaybackVoiceChannel { get; private set; } @@ -250,11 +250,11 @@ namespace NadekoBot.Modules.Music.Classes { var curSong = CurrentSong; var toUpdate = playlist.Where(s => s.SongInfo.ProviderType == MusicType.Normal && - s.TotalLength == TimeSpan.Zero); + s.TotalTime == TimeSpan.Zero); if (curSong != null) toUpdate = toUpdate.Append(curSong); var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3)) - .Distinct(); + .Distinct(); var durations = await NadekoBot.Google.GetVideoDurationsAsync(ids); @@ -264,11 +264,12 @@ namespace NadekoBot.Modules.Music.Classes { if (s.SongInfo.Query.EndsWith(kvp.Key)) { - s.TotalLength = kvp.Value; + s.TotalTime = kvp.Value; return; } } }); + } public void Destroy() diff --git a/src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs b/src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs new file mode 100644 index 00000000..2840fd16 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs @@ -0,0 +1,15 @@ +ο»Ώusing Discord; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Music.Classes +{ + public static class MusicExtensions + { + public static EmbedAuthorBuilder WithMusicIcon(this EmbedAuthorBuilder eab) => + eab.WithIconUrl("http://i.imgur.com/nhKS3PT.png"); + } +} diff --git a/src/NadekoBot/Modules/Music/Classes/Song.cs b/src/NadekoBot/Modules/Music/Classes/Song.cs index e80a16be..71daa7ec 100644 --- a/src/NadekoBot/Modules/Music/Classes/Song.cs +++ b/src/NadekoBot/Modules/Music/Classes/Song.cs @@ -18,60 +18,54 @@ namespace NadekoBot.Modules.Music.Classes { public string Provider { get; set; } public MusicType ProviderType { get; set; } - /// - /// Will be set only if the providertype is normal - /// public string Query { get; set; } public string Title { get; set; } public string Uri { get; set; } - public string AlbumArt { get; set; } + public string AlbumArt { get; set; } } + public class Song { - public StreamState State { get; set; } - public string PrettyName => - $"**{SongInfo.Title.TrimTo(55)} `{(SongInfo.Provider ?? "-")} by {QueuerName}`**"; - //$"{SongInfo.Title.TrimTo(70)}"; public SongInfo SongInfo { get; } public MusicPlayer MusicPlayer { get; set; } - - public string PrettyUser => - $"{QueuerName}"; public string QueuerName { get; set; } - - public string PrettyProvider => - $"{(SongInfo.Provider ?? "No Provider")}"; - public string PrettyCurrentTime() - { - var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50); - //var str = $"{(int)time.TotalMinutes}m {time.Seconds}s / "; - var str = $""; - if (TotalLength == TimeSpan.Zero) - str += "(?)"; - else if (TotalLength == TimeSpan.MaxValue) - str += "**∞**"; - else - str += $"{(int)TotalLength.TotalMinutes}m {TotalLength.Seconds}s"; - return str; - } - public string PrettyMusicPlayTime() - { - var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50); - var str = $"{(int)time.TotalMinutes}m {time.Seconds}s"; - return str; - } + public TimeSpan TotalTime { get; set; } = TimeSpan.Zero; + public TimeSpan CurrentTime => TimeSpan.FromSeconds(bytesSent / frameBytes / 1000 / milliseconds); + const int milliseconds = 20; const int samplesPerFrame = (48000 / 1000) * milliseconds; const int frameBytes = 3840; //16-bit, 2 channels private ulong bytesSent { get; set; } = 0; - public bool PrintStatusMessage { get; set; } = true; + //pwetty + + public string PrettyProvider => + $"{(SongInfo.Provider ?? "No Provider")}"; + + public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime; + + public string PrettyName => $"**[{SongInfo.Title.TrimTo(70)}]({SongInfo.Query})**"; + + public string PrettyInfo => $"{PrettyTotalTime} | {PrettyProvider} | {QueuerName}"; + + public string PrettyFullName => $"{PrettyName}\n\t\t*{PrettyInfo}*"; + + public string PrettyCurrentTime => TotalTime.ToString(@"mm\:ss"); + + private string PrettyTotalTime { + get { + if (TotalTime == TimeSpan.Zero) + return "(?)"; + else if (TotalTime == TimeSpan.MaxValue) + return "**∞**"; + else + return TotalTime.ToString(@"mm\:ss"); + } + } private int skipTo = 0; - private Logger _log; - public int SkipTo { get { return skipTo; } set { @@ -80,7 +74,7 @@ namespace NadekoBot.Modules.Music.Classes } } - public TimeSpan TotalLength { get; set; } = TimeSpan.Zero; + private readonly Logger _log; public Song(SongInfo songInfo) { @@ -92,16 +86,9 @@ namespace NadekoBot.Modules.Music.Classes { var s = new Song(SongInfo); s.MusicPlayer = MusicPlayer; - s.State = StreamState.Queued; return s; } - public Song SetMusicPlayer(MusicPlayer mp) - { - this.MusicPlayer = mp; - return this; - } - public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) { var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString()); @@ -111,7 +98,7 @@ namespace NadekoBot.Modules.Music.Classes try { - var attempt = 0; + var attempt = 0; var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy var finished = false; @@ -132,7 +119,7 @@ namespace NadekoBot.Modules.Music.Classes _log.Warn("Slow connection buffering more to ensure no disruption, consider hosting in cloud"); continue; } - + if (inStream.BufferingCompleted && count == 1) { _log.Debug("Prebuffering canceled. Cannot get any data from the stream."); @@ -142,7 +129,7 @@ namespace NadekoBot.Modules.Music.Classes { continue; } - } + } else if (prebufferingTask.IsCanceled) { _log.Debug("Prebuffering canceled. Cannot get any data from the stream."); @@ -151,7 +138,7 @@ namespace NadekoBot.Modules.Music.Classes finished = true; } sw.Stop(); - _log.Debug("Prebuffering successfully completed in "+ sw.Elapsed); + _log.Debug("Prebuffering successfully completed in " + sw.Elapsed); var outStream = voiceClient.CreatePCMStream(960); @@ -163,7 +150,7 @@ namespace NadekoBot.Modules.Music.Classes //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); //await inStream.CopyToAsync(voiceClient.OutputStream); - if(read < frameBytes) + if (read < frameBytes) _log.Debug("read {0}", read); unchecked { @@ -210,7 +197,7 @@ namespace NadekoBot.Modules.Music.Classes finally { await bufferTask; - if(inStream != null) + if (inStream != null) inStream.Dispose(); } } @@ -224,35 +211,6 @@ namespace NadekoBot.Modules.Music.Classes _log.Debug("Buffering successfull"); } - /* - //stackoverflow ftw - private static byte[] AdjustVolume(byte[] audioSamples, float volume) - { - if (Math.Abs(volume - 1.0f) < 0.01f) - return audioSamples; - var array = new byte[audioSamples.Length]; - for (var i = 0; i < array.Length; i += 2) - { - - // convert byte pair to int - short buf1 = audioSamples[i + 1]; - short buf2 = audioSamples[i]; - - buf1 = (short)((buf1 & 0xff) << 8); - buf2 = (short)(buf2 & 0xff); - - var res = (short)(buf1 | buf2); - res = (short)(res * volume); - - // convert back - array[i] = (byte)res; - array[i + 1] = (byte)(res >> 8); - - } - return array; - } - */ - //aidiakapi ftw public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume) { @@ -278,202 +236,5 @@ namespace NadekoBot.Modules.Music.Classes return audioSamples; } - - public static async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - - if (musicType != MusicType.Local && IsRadioLink(query)) - { - musicType = MusicType.Radio; - query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query; - } - - try - { - switch (musicType) - { - case MusicType.Local: - return new Song(new SongInfo - { - Uri = "\"" + Path.GetFullPath(query) + "\"", - Title = Path.GetFileNameWithoutExtension(query), - Provider = "Local File", - ProviderType = musicType, - Query = query, - }); - case MusicType.Radio: - return new Song(new SongInfo - { - Uri = query, - Title = $"{query}", - Provider = "Radio Stream", - ProviderType = musicType, - Query = query - }) - { TotalLength = TimeSpan.MaxValue }; - } - if (SoundCloud.Default.IsSoundCloudLink(query)) - { - var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false); - return new Song(new SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = svideo.StreamLink, - ProviderType = musicType, - Query = svideo.TrackLink, - AlbumArt = svideo.artwork_url, - }) - { TotalLength = TimeSpan.FromMilliseconds(svideo.Duration) }; - } - - if (musicType == MusicType.Soundcloud) - { - var svideo = await SoundCloud.Default.GetVideoByQueryAsync(query).ConfigureAwait(false); - return new Song(new SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = svideo.StreamLink, - ProviderType = MusicType.Normal, - Query = svideo.TrackLink, - AlbumArt = svideo.artwork_url, - }) - { TotalLength = TimeSpan.FromMilliseconds(svideo.Duration) }; - } - - var link = (await NadekoBot.Google.GetVideosByKeywordsAsync(query).ConfigureAwait(false)).FirstOrDefault(); - if (string.IsNullOrWhiteSpace(link)) - throw new OperationCanceledException("Not a valid youtube query."); - var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty(); } }).ConfigureAwait(false); - var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio); - var video = videos - .Where(v => v.AudioBitrate < 256) - .OrderByDescending(v => v.AudioBitrate) - .FirstOrDefault(); - - if (video == null) // do something with this error - throw new Exception("Could not load any video elements based on the query."); - var m = Regex.Match(query, @"\?t=(?\d*)"); - int gotoTime = 0; - if (m.Captures.Count > 0) - int.TryParse(m.Groups["t"].ToString(), out gotoTime); - var song = new Song(new SongInfo - { - Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" - Provider = "YouTube", - Uri = video.Uri, - Query = link, - ProviderType = musicType, - }); - song.SkipTo = gotoTime; - return song; - } - catch (Exception ex) - { - Console.WriteLine($"Failed resolving the link.{ex.Message}"); - return null; - } - } - - private static async Task HandleStreamContainers(string query) - { - string file = null; - try - { - using (var http = new HttpClient()) - { - file = await http.GetStringAsync(query).ConfigureAwait(false); - } - } - catch - { - return query; - } - if (query.Contains(".pls")) - { - //File1=http://armitunes.com:8000/ - //Regex.Match(query) - try - { - var m = Regex.Match(file, "File1=(?.*?)\\n"); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .pls:\n{file}"); - return null; - } - } - if (query.Contains(".m3u")) - { - /* -# This is a comment - C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 - C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 - */ - try - { - var m = Regex.Match(file, "(?^[^#].*)", RegexOptions.Multiline); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .m3u:\n{file}"); - return null; - } - - } - if (query.Contains(".asx")) - { - // - try - { - var m = Regex.Match(file, ".*?)\""); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .asx:\n{file}"); - return null; - } - } - if (query.Contains(".xspf")) - { - /* - - - - file:///mp3s/song_1.mp3 - */ - try - { - var m = Regex.Match(file, "(?.*?)"); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - Console.WriteLine($"Failed reading .xspf:\n{file}"); - return null; - } - } - - return query; - } - - private static bool IsRadioLink(string query) => - (query.StartsWith("http") || - query.StartsWith("ww")) - && - (query.Contains(".pls") || - query.Contains(".m3u") || - query.Contains(".asx") || - query.Contains(".xspf")); } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Classes/SongHandler.cs b/src/NadekoBot/Modules/Music/Classes/SongHandler.cs new file mode 100644 index 00000000..50166561 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Classes/SongHandler.cs @@ -0,0 +1,212 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using VideoLibrary; + +namespace NadekoBot.Modules.Music.Classes +{ + public static class SongHandler + { + public static async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + + if (musicType != MusicType.Local && IsRadioLink(query)) + { + musicType = MusicType.Radio; + query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query; + } + + try + { + switch (musicType) + { + case MusicType.Local: + return new Song(new SongInfo + { + Uri = "\"" + Path.GetFullPath(query) + "\"", + Title = Path.GetFileNameWithoutExtension(query), + Provider = "Local File", + ProviderType = musicType, + Query = query, + }); + case MusicType.Radio: + return new Song(new SongInfo + { + Uri = query, + Title = $"{query}", + Provider = "Radio Stream", + ProviderType = musicType, + Query = query + }) + { TotalTime = TimeSpan.MaxValue }; + } + if (SoundCloud.Default.IsSoundCloudLink(query)) + { + var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false); + return new Song(new SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = musicType, + Query = svideo.TrackLink, + AlbumArt = svideo.artwork_url, + }) + { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; + } + + if (musicType == MusicType.Soundcloud) + { + var svideo = await SoundCloud.Default.GetVideoByQueryAsync(query).ConfigureAwait(false); + return new Song(new SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = MusicType.Normal, + Query = svideo.TrackLink, + AlbumArt = svideo.artwork_url, + }) + { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; + } + + var link = (await NadekoBot.Google.GetVideosByKeywordsAsync(query).ConfigureAwait(false)).FirstOrDefault(); + if (string.IsNullOrWhiteSpace(link)) + throw new OperationCanceledException("Not a valid youtube query."); + var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty(); } }).ConfigureAwait(false); + var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio); + var video = videos + .Where(v => v.AudioBitrate < 256) + .OrderByDescending(v => v.AudioBitrate) + .FirstOrDefault(); + + if (video == null) // do something with this error + throw new Exception("Could not load any video elements based on the query."); + var m = Regex.Match(query, @"\?t=(?\d*)"); + int gotoTime = 0; + if (m.Captures.Count > 0) + int.TryParse(m.Groups["t"].ToString(), out gotoTime); + var song = new Song(new SongInfo + { + Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" + Provider = "YouTube", + Uri = video.Uri, + Query = link, + ProviderType = musicType, + }); + song.SkipTo = gotoTime; + return song; + } + catch (Exception ex) + { + Console.WriteLine($"Failed resolving the link.{ex.Message}"); + return null; + } + } + + private static async Task HandleStreamContainers(string query) + { + string file = null; + try + { + using (var http = new HttpClient()) + { + file = await http.GetStringAsync(query).ConfigureAwait(false); + } + } + catch + { + return query; + } + if (query.Contains(".pls")) + { + //File1=http://armitunes.com:8000/ + //Regex.Match(query) + try + { + var m = Regex.Match(file, "File1=(?.*?)\\n"); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + Console.WriteLine($"Failed reading .pls:\n{file}"); + return null; + } + } + if (query.Contains(".m3u")) + { + /* +# This is a comment + C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3 + C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3 + */ + try + { + var m = Regex.Match(file, "(?^[^#].*)", RegexOptions.Multiline); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + Console.WriteLine($"Failed reading .m3u:\n{file}"); + return null; + } + + } + if (query.Contains(".asx")) + { + // + try + { + var m = Regex.Match(file, ".*?)\""); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + Console.WriteLine($"Failed reading .asx:\n{file}"); + return null; + } + } + if (query.Contains(".xspf")) + { + /* + + + + file:///mp3s/song_1.mp3 + */ + try + { + var m = Regex.Match(file, "(?.*?)"); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + Console.WriteLine($"Failed reading .xspf:\n{file}"); + return null; + } + } + + return query; + } + + private static bool IsRadioLink(string query) => + (query.StartsWith("http") || + query.StartsWith("ww")) + && + (query.Contains(".pls") || + query.Contains(".m3u") || + query.Contains(".asx") || + query.Contains(".xspf")); + } +} diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index 0bd7144d..64eb905c 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -29,13 +29,13 @@ namespace NadekoBot.Modules.Music { //it can fail if its currenctly opened or doesn't exist. Either way i don't care try { Directory.Delete(MusicDataPath, true); } catch { } - - NadekoBot.Client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated; + + NadekoBot.Client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated; Directory.CreateDirectory(MusicDataPath); } - - private Task Client_UserVoiceStateUpdated(IUser iusr, IVoiceState oldState, IVoiceState newState) + + private Task Client_UserVoiceStateUpdated(IUser iusr, IVoiceState oldState, IVoiceState newState) { var usr = iusr as IGuildUser; if (usr == null || @@ -98,12 +98,12 @@ namespace NadekoBot.Modules.Music [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public async Task Destroy(IUserMessage umsg) + public async Task Destroy(IUserMessage umsg) //public Task Destroy(IUserMessage umsg) { var channel = (ITextChannel)umsg.Channel; - await channel.SendErrorAsync("Command is temporarily disabled.").ConfigureAwait(false); - + await channel.SendErrorAsync("This command is temporarily disabled.").ConfigureAwait(false); + /*MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return Task.CompletedTask; if (((IGuildUser)umsg.Author).VoiceChannel == musicPlayer.PlaybackVoiceChannel) @@ -175,55 +175,35 @@ namespace NadekoBot.Modules.Music return; } - if (currentSong.TotalLength == TimeSpan.Zero) - { - await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); - } - + try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { } - //var toSend = $"🎡 Currently Playing {currentSong.PrettyName} " + $"`{currentSong.PrettyCurrentTime()}`\n"; - //var toSend = $"🎡 Currently Playing {currentSong.PrettyName}\n"; - - //I did that ^ because current song, bugs when a youtube playlist is queued with more than 50 song, it more like a bug with youtube page token I believe. - - const int itemsPerPage = 10; + const int itemsPerPage = 10; int startAt = itemsPerPage * (page - 1); - var number = 1 + startAt; + var number = 0 + startAt; - var embed = new EmbedBuilder() - .WithAuthor(eab => eab.WithName($"Track List: Page {page}").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .WithDescription(string.Join("\n", musicPlayer.Playlist.Skip(startAt).Take(10).Select(v => $"`{number++}.` **[{v.SongInfo.Title.TrimTo(70)}]({v.SongInfo.Query})**\n\t\t*{v.PrettyCurrentTime()}* **|** *{v.PrettyProvider}* **|** *{v.QueuerName}*"))) - .WithFooter(ef => ef.WithText($"{musicPlayer.Playlist.Count} tracks currently queued.")) - .WithColor(NadekoBot.OkColor); - if (musicPlayer.RepeatSong) - { - embed.WithTitle($"πŸ”‚ Repeating Song: {currentSong.SongInfo.Title} | {currentSong.PrettyMusicPlayTime()} / {currentSong.PrettyCurrentTime()}"); - } - else if (musicPlayer.RepeatPlaylist) - { - embed.WithTitle("πŸ” Repeating Playlist"); - } - if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) - { - embed.WithTitle("🎡 Song queue is full!"); - } - await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); - - - //var toSend = $"test"; //this was for testing - //if (musicPlayer.RepeatSong) - //toSend += "πŸ”‚"; - //else if (musicPlayer.RepeatPlaylist) - //toSend += "πŸ”"; - //toSend += $" `{musicPlayer.Playlist.Count} tracks currently queued. Showing page {page}:` "; - //if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) - //toSend += "**Song queue is full!**\n"; - //else - //toSend += "\n"; - //const int itemsPerPage = 15; - //int startAt = itemsPerPage * (page - 1); - //var number = 1 + startAt; - //await channel.SendConfirmAsync(toSend + string.Join("\n", musicPlayer.Playlist.Skip(startAt).Take(15).Select(v => $"`{number++}.` {v.PrettyName}"))).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithAuthor(eab => eab.WithName($"Player Queue: Page {page}") + .WithMusicIcon()) + .WithDescription(string.Join("\n", musicPlayer.Playlist + .Skip(startAt) + .Take(10) + .Select(v => $"`{++number}.` {v.PrettyFullName}"))) + .WithFooter(ef => ef.WithText($"{musicPlayer.Playlist.Count} tracks currently queued.")) + .WithOkColor(); + + if (musicPlayer.RepeatSong) + { + embed.WithTitle($"πŸ”‚ Repeating Song: {currentSong.SongInfo.Title} | {currentSong.PrettyFullTime}"); + } + else if (musicPlayer.RepeatPlaylist) + { + embed.WithTitle("πŸ” Repeating Playlist"); + } + if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) + { + embed.WithTitle("🎡 Song queue is full!"); + } + await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -231,53 +211,23 @@ namespace NadekoBot.Modules.Music public async Task NowPlaying(IUserMessage umsg) { var channel = (ITextChannel)umsg.Channel; - + MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return; var currentSong = musicPlayer.CurrentSong; if (currentSong == null) return; - var videoid = Regex.Match(currentSong.SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+"); - - var embed = new EmbedBuilder() - .WithAuthor(eab => eab.WithName("Now Playing").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .WithTitle($"{currentSong.SongInfo.Title}") - //.WithDescription($"{currentSong.PrettyCurrentTime()}") - .WithFooter(ef => ef.WithText($"{currentSong.PrettyProvider} | {currentSong.QueuerName}")) - .WithColor(NadekoBot.OkColor); - if (currentSong.SongInfo.Provider.Equals("YouTube", StringComparison.OrdinalIgnoreCase)) - { - embed.WithThumbnail(tn => tn.Url = $"https://img.youtube.com/vi/{videoid}/0.jpg"); - embed.WithUrl($"{currentSong.SongInfo.Query}"); - //if (musicPlayer.Playlist.Count < 50) - //{ - if (currentSong.TotalLength == TimeSpan.Zero) - { - await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); - } - embed.WithDescription($"{currentSong.PrettyMusicPlayTime()} / {currentSong.PrettyCurrentTime()}"); - //} - //else if (musicPlayer.Playlist.Count > 50) - //{ - //embed.WithDescription($"{currentSong.PrettyMusicPlayTime()}"); - //} - } - else if (currentSong.SongInfo.Provider.Equals("SoundCloud", StringComparison.OrdinalIgnoreCase)) - { - embed.WithThumbnail(tn => tn.Url = $"{currentSong.SongInfo.AlbumArt}"); - embed.WithUrl($"{currentSong.SongInfo.Query}"); - if (currentSong.TotalLength == TimeSpan.Zero) - { - await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); - } - embed.WithDescription($"{currentSong.PrettyMusicPlayTime()} / {currentSong.PrettyCurrentTime()}"); - } - else if (currentSong.SongInfo.Provider.Equals("Local File", StringComparison.OrdinalIgnoreCase)) - { - embed.WithDescription($"{currentSong.PrettyMusicPlayTime()}"); - } - await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); + var videoid = Regex.Match(currentSong.SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+"); + + var embed = new EmbedBuilder() + .WithAuthor(eab => eab.WithName("Now Playing") + .WithMusicIcon()) + .WithDescription(currentSong.PrettyName) + .WithFooter(ef => ef.WithText(currentSong.PrettyCurrentTime + "/" + currentSong.PrettyInfo)) + .WithOkColor(); + + await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -365,19 +315,18 @@ namespace NadekoBot.Modules.Music var msg = await channel.SendMessageAsync($"🎡 Attempting to queue **{count}** songs".SnPl(count) + "...").ConfigureAwait(false); - + foreach (var id in idArray) { try { await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, id, true).ConfigureAwait(false); - //await Task.Delay(2000).ConfigureAwait(false); //fixes google api error for few songs on playlist.// } catch (SongNotFoundException) { } catch { break; } - + } - + await msg.ModifyAsync(m => m.Content = "βœ… Playlist queue complete.").ConfigureAwait(false); } @@ -511,12 +460,12 @@ namespace NadekoBot.Modules.Music var song = (musicPlayer.Playlist as List)?[num - 1]; musicPlayer.RemoveSongAt(num - 1); //await channel.SendConfirmAsync($"🎡 Track {song.PrettyName} at position `#{num}` has been **removed**.").ConfigureAwait(false); - var embed = new EmbedBuilder() - .WithAuthor(eab => eab.WithName("Song Removed!").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .AddField(fb => fb.WithName("**Song Position**").WithValue($"#{num}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Song Name**").WithValue($"**[{song.SongInfo.Title.TrimTo(70)}]({song.SongInfo.Query})** `{song.PrettyProvider} | {song.QueuerName.TrimTo(15)}`").WithIsInline(true)) - .WithColor(NadekoBot.ErrorColor); - await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithAuthor(eab => eab.WithName("Song Removed!").WithMusicIcon()) + .AddField(fb => fb.WithName("**Song Position**").WithValue($"#{num}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Song Name**").WithValue($"**[{song.SongInfo.Title.TrimTo(70)}]({song.SongInfo.Query})** `{song.PrettyProvider} | {song.QueuerName.TrimTo(15)}`").WithIsInline(true)) + .WithColor(NadekoBot.ErrorColor); + await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -565,19 +514,15 @@ namespace NadekoBot.Modules.Music playlist.Insert(n2 - 1, s); var nn1 = n2 < n1 ? n1 : n1 - 1; playlist.RemoveAt(nn1); - - var embed = new EmbedBuilder() - .WithTitle($"{s.SongInfo.Title.TrimTo(70)}") - .WithUrl($"{s.SongInfo.Query}") - .WithAuthor(eab => eab.WithName("Song Moved").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .AddField(fb => fb.WithName("**From Position**").WithValue($"#{n1}").WithIsInline(true)) - .AddField(fb => fb.WithName("**To Position**").WithValue($"#{n2}").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); + + var embed = new EmbedBuilder() + .WithTitle($"{s.SongInfo.Title.TrimTo(70)}") + .WithUrl($"{s.SongInfo.Query}") + .WithAuthor(eab => eab.WithName("Song Moved").WithMusicIcon()) + .AddField(fb => fb.WithName("**From Position**").WithValue($"#{n1}").WithIsInline(true)) + .AddField(fb => fb.WithName("**To Position**").WithValue($"#{n2}").WithIsInline(true)) + .WithOkColor(); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); - - //await channel.SendConfirmAsync($"🎡Moved {s.PrettyName} `from #{n1} to #{n2}`").ConfigureAwait(false); - - } [NadekoCommand, Usage, Description, Aliases] @@ -606,9 +551,15 @@ namespace NadekoBot.Modules.Music if (currentSong == null) return; var currentValue = musicPlayer.ToggleRepeatSong(); - await channel.SendConfirmAsync(currentValue ? - $"πŸ”‚ Repeating track: {currentSong.PrettyName}" : - $"πŸ”‚ Current track repeat stopped.") + + if (currentValue) + await channel.EmbedAsync(new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithMusicIcon().WithName("πŸ”‚ Repeating track")) + .WithDescription(currentSong.PrettyFullName) + .Build()).ConfigureAwait(false); + else + await channel.SendConfirmAsync($"πŸ”‚ Current track repeat stopped.") .ConfigureAwait(false); } @@ -635,7 +586,8 @@ namespace NadekoBot.Modules.Music var curSong = musicPlayer.CurrentSong; var songs = musicPlayer.Playlist.Append(curSong) - .Select(s=> new PlaylistSong() { + .Select(s => new PlaylistSong() + { Provider = s.SongInfo.Provider, ProviderType = s.SongInfo.ProviderType, Title = s.SongInfo.Title, @@ -711,14 +663,14 @@ namespace NadekoBot.Modules.Music //await channel.SendConfirmAsync($@"🎢 **Page {num} of saved playlists:** -//" + string.Join("\n", playlists.Select(r => $"`#{r.Id}` - **{r.Name}** by __{r.Author}__ ({r.Songs.Count} songs)"))).ConfigureAwait(false); + //" + string.Join("\n", playlists.Select(r => $"`#{r.Id}` - **{r.Name}** by __{r.Author}__ ({r.Songs.Count} songs)"))).ConfigureAwait(false); - var embed = new EmbedBuilder() - .WithAuthor(eab => eab.WithName($"Page {num} of Saved Playlists").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .WithDescription(string.Join("\n", playlists.Select(r => $"`#{r.Id}` - **{r.Name}**\t by **`{r.Author}`**\t ({r.Songs.Count} songs)"))) - .WithColor(NadekoBot.OkColor); + var embed = new EmbedBuilder() + .WithAuthor(eab => eab.WithName($"Page {num} of Saved Playlists").WithMusicIcon()) + .WithDescription(string.Join("\n", playlists.Select(r => $"`#{r.Id}` - **{r.Name}**\t by **`{r.Author}`**\t ({r.Songs.Count} songs)"))) + .WithOkColor(); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); - + } //todo only author or owner @@ -866,116 +818,63 @@ namespace NadekoBot.Modules.Music vol = uow.GuildConfigs.For(textCh.Guild.Id, set => set).DefaultMusicVolume; } var mp = new MusicPlayer(voiceCh, vol); - IUserMessage playingMessage = null; - IUserMessage lastFinishedMessage = null; + + IUserMessage finishedMessage = null; mp.OnCompleted += async (s, song) => - { - if (song.PrintStatusMessage) - { - try - { - if (lastFinishedMessage != null) - { - await lastFinishedMessage.DeleteAsync().ConfigureAwait(false); - //try { lastFinishedMessage = await textCh.SendConfirmAsync($"🎡 Finished {song.PrettyName}").ConfigureAwait(false); } catch { } - try { lastFinishedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) - .WithAuthor(eab => eab.WithName("Finished Song").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .WithTitle($"{song.SongInfo.Title}") - .WithFooter(ef => ef.WithText($"{song.PrettyProvider} | {song.QueuerName}")) - .Build()) - .ConfigureAwait(false); } catch { } - } - else - { - try { lastFinishedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) - .WithAuthor(eab => eab.WithName("Finished Song").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .WithTitle($"{song.SongInfo.Title}") - .WithFooter(ef => ef.WithText($"{song.PrettyProvider} | {song.QueuerName}")) - .Build()) - .ConfigureAwait(false); } catch { } - //try { lastFinishedMessage = await textCh.SendConfirmAsync($"🎡 Finished {song.PrettyName}").ConfigureAwait(false); } catch { } - } - if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube") - { - await QueueSong(queuer.Guild.GetCurrentUser(), textCh, voiceCh, (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false); - } - } - catch { } - } - }; - mp.OnStarted += async (s, song) => - { - if (song.PrintStatusMessage) - { - var sender = s as MusicPlayer; - //var msgTxt = $"🎡 Playing {song.PrettyName}\t `Vol: {(int)(sender.Volume * 100)}%`"; - if (sender == null) - return; - if (playingMessage != null) - { - await playingMessage.DeleteAsync().ConfigureAwait(false); - //try { playingMessage = await textCh.SendConfirmAsync(msgTxt).ConfigureAwait(false); } catch { } - try { playingMessage = await textCh.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) - .WithAuthor(eab => eab.WithName("Playing Song").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .WithTitle($"{song.SongInfo.Title}") - .WithDescription($"Volume: {(int)(sender.Volume * 100)}%") - .WithFooter(ef => ef.WithText($"{song.PrettyProvider} | {song.QueuerName}")) - .Build()) - .ConfigureAwait(false); } catch { } - } - else - { - //try { playingMessage = await textCh.SendConfirmAsync(msgTxt).ConfigureAwait(false); } catch { } - try { playingMessage = await textCh.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) - .WithAuthor(eab => eab.WithName("Playing Song").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .WithTitle($"{song.SongInfo.Title}") - .WithDescription($"Volume: {(int)(sender.Volume * 100)}%") - .WithFooter(ef => ef.WithText($"{song.PrettyProvider} | {song.QueuerName}")) - .Build()) - .ConfigureAwait(false); } catch { } - } - } - }; - IUserMessage resumemsg = null; - IUserMessage pausemsg = null; - mp.OnPauseChanged += async (paused) => { try { - if (paused) - { - if (pausemsg != null) - { - await pausemsg.DeleteAsync().ConfigureAwait(false); - try { pausemsg = await textCh.SendConfirmAsync("🎡 Music playback **paused**.").ConfigureAwait(false); } catch { } - } - else - { - try { pausemsg = await textCh.SendConfirmAsync("🎡 Music playback **paused**.").ConfigureAwait(false); } catch { } - } - } - else - { - if (resumemsg != null) - { - await resumemsg.DeleteAsync().ConfigureAwait(false); - try { resumemsg = await textCh.SendConfirmAsync("🎡 Music playback **resumed**.").ConfigureAwait(false); } catch { } - } - else - { - try { resumemsg = await textCh.SendConfirmAsync("🎡 Music playback **resumed**.").ConfigureAwait(false); } catch { } - } - } + if (finishedMessage != null) + finishedMessage.DeleteAfter(0); + finishedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor() + .WithAuthor(eab => eab.WithName("Finished Song").WithMusicIcon()) + .WithDescription(song.PrettyName) + .WithFooter(ef => ef.WithText(song.PrettyInfo)) + .Build()) + .ConfigureAwait(false); + + if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube") + { + await QueueSong(queuer.Guild.GetCurrentUser(), textCh, voiceCh, (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false); + } } catch { } }; + IUserMessage playingMessage = null; + mp.OnStarted += async (player, song) => + { + if (playingMessage != null) + playingMessage.DeleteAfter(0); + + playingMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor() + .WithAuthor(eab => eab.WithName("Playing Song").WithMusicIcon()) + .WithDescription(song.PrettyName) + .WithFooter(ef => ef.WithText(song.PrettyInfo)) + .Build()) + .ConfigureAwait(false); + }; + + mp.OnPauseChanged += async (paused) => + { + IUserMessage pauseMessage = null; + if (paused) + { + pauseMessage = await textCh.SendConfirmAsync("🎡 Music playback **paused**.").ConfigureAwait(false); + } + else + { + pauseMessage = await textCh.SendConfirmAsync("🎡 Music playback **resumed**.").ConfigureAwait(false); + } + if (pauseMessage != null) + pauseMessage.DeleteAfter(15); + }; return mp; }); Song resolvedSong; try { musicPlayer.ThrowIfQueueFull(); - resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false); + resolvedSong = await SongHandler.ResolveSong(query, musicType).ConfigureAwait(false); if (resolvedSong == null) throw new SongNotFoundException(); @@ -992,19 +891,19 @@ namespace NadekoBot.Modules.Music try { //var queuedMessage = await textCh.SendConfirmAsync($"🎡 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false); - var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) - .WithAuthor(eab => eab.WithName("Queued Song").WithIconUrl("http://i.imgur.com/nhKS3PT.png")) - .WithTitle($"{resolvedSong.SongInfo.Title}") - .WithDescription($"Queue #{musicPlayer.Playlist.Count + 1}") - .WithFooter(ef => ef.WithText($"{resolvedSong.PrettyProvider}")) + var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor() + .WithAuthor(eab => eab.WithName("Queued Song").WithMusicIcon()) + .WithTitle($"{resolvedSong.SongInfo.Title}") + .WithDescription($"Queue #{musicPlayer.Playlist.Count + 1}") + .WithFooter(ef => ef.WithText($"{resolvedSong.PrettyProvider}")) .Build()) .ConfigureAwait(false); - var t = Task.Run(async () => + var t = Task.Run(async () => { try { await Task.Delay(10000).ConfigureAwait(false); - + await queuedMessage.DeleteAsync().ConfigureAwait(false); } catch { } @@ -1014,4 +913,4 @@ namespace NadekoBot.Modules.Music } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Pokemon/Pokemon.cs b/src/NadekoBot/Modules/Pokemon/Pokemon.cs index 31dbf2cb..0861bc1d 100644 --- a/src/NadekoBot/Modules/Pokemon/Pokemon.cs +++ b/src/NadekoBot/Modules/Pokemon/Pokemon.cs @@ -316,7 +316,7 @@ namespace NadekoBot.Modules.Pokemon var targetType = StringToPokemonType(typeTargeted); if (targetType == null) { - await channel.EmbedAsync(PokemonTypes.Aggregate(new EmbedBuilder().WithDescription("List of the available types:"), (eb, pt) => eb.AddField(efb => efb.WithName(pt.Name).WithValue(pt.Icon).WithIsInline(true))).WithColor(NadekoBot.OkColor).Build()).ConfigureAwait(false); + await channel.EmbedAsync(PokemonTypes.Aggregate(new EmbedBuilder().WithDescription("List of the available types:"), (eb, pt) => eb.AddField(efb => efb.WithName(pt.Name).WithValue(pt.Icon).WithIsInline(true))).WithOkColor().Build()).ConfigureAwait(false); return; } if (targetType == GetPokeType(user.Id)) diff --git a/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs b/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs index e50f2060..25c18267 100644 --- a/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs @@ -51,7 +51,7 @@ namespace NadekoBot.Modules.Searches $"limit={showCount}") .ConfigureAwait(false))["data"] as JArray; var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList(); - var eb = new EmbedBuilder().WithColor(NadekoBot.OkColor).WithTitle(Format.Underline($"{dataList.Count} most banned champions")); + var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline($"{dataList.Count} most banned champions")); for (var i = 0; i < dataList.Count; i++) { var champ = dataList[i]; diff --git a/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs b/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs index 02373bb3..b73973a2 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs +++ b/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs @@ -1,5 +1,6 @@ ο»Ώusing Discord; using Discord.API; +using NadekoBot.Extensions; using Newtonsoft.Json; using System; using System.Net.Http; @@ -35,7 +36,7 @@ namespace NadekoBot.Modules.Searches.Commands.OMDB public string Poster { get; set; } public Embed GetEmbed() => - new EmbedBuilder().WithColor(NadekoBot.OkColor) + new EmbedBuilder().WithOkColor() .WithTitle(Title) .WithUrl($"http://www.imdb.com/title/{ImdbId}/") .WithDescription(Plot) diff --git a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs index 69697608..75da203a 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs @@ -53,7 +53,7 @@ namespace NadekoBot.Modules.Searches .AddField(fb => fb.WithName("**Competitive Rank**").WithValue("0").WithIsInline(true)) .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } else @@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Searches .AddField(fb => fb.WithName("**Competitive Rank**").WithValue(rank).WithIsInline(true)) .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } if (string.IsNullOrWhiteSpace(competitiveplay)) @@ -85,7 +85,7 @@ namespace NadekoBot.Modules.Searches .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"0 hour").WithIsInline(true)) .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } } diff --git a/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs index 60b9c27e..36ead93d 100644 --- a/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs @@ -55,7 +55,7 @@ namespace NadekoBot.Modules.Searches if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant()) { var p = kvp.Value; - await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) + await channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithTitle(kvp.Key.ToTitleCase()) .WithDescription(p.BaseStats.ToString()) .AddField(efb => efb.WithName("Types").WithValue(string.Join(",\n", p.Types)).WithIsInline(true)) @@ -81,7 +81,7 @@ namespace NadekoBot.Modules.Searches { if (kvp.Key.ToUpperInvariant() == ability) { - await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) + await channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithTitle(kvp.Value.Name) .WithDescription(kvp.Value.Desc) .AddField(efb => efb.WithName("Rating").WithValue(kvp.Value.Rating.ToString()).WithIsInline(true)) diff --git a/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs b/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs index f5700698..49478c2c 100644 --- a/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs @@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Searches var res = await http.GetStringAsync($"{xkcdUrl}/{num}/info.0.json").ConfigureAwait(false); var comic = JsonConvert.DeserializeObject(res); - var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor) + var embed = new EmbedBuilder().WithOkColor() .WithImage(eib => eib.WithUrl(comic.ImageLink)) .WithAuthor(eab => eab.WithName(comic.Title).WithUrl($"{xkcdUrl}/{num}").WithIconUrl("http://xkcd.com/s/919f27.ico")) .AddField(efb => efb.WithName("Comic#").WithValue(comic.Num.ToString()).WithIsInline(true)) diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index c7e5367a..b51eaa03 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -46,7 +46,7 @@ namespace NadekoBot.Modules.Searches .AddField(fb => fb.WithName("πŸ”† **Feels like**").WithValue($"{obj["feelscentigrade"]}Β°C ({obj["feelsfahrenheit"]}Β°F)").WithIsInline(true)) .AddField(fb => fb.WithName("πŸŒ„ **Sunrise**").WithValue($"{obj["sunrise"]}").WithIsInline(true)) .AddField(fb => fb.WithName("πŸŒ‡ **Sunset**").WithValue($"{obj["sunset"]}").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } @@ -202,7 +202,7 @@ namespace NadekoBot.Modules.Searches await msg.Channel.SendErrorAsync("Failed to shorten that url.").ConfigureAwait(false); } - await msg.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) + await msg.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() .AddField(efb => efb.WithName("Original Url") .WithValue($"<{arg}>")) .AddField(efb => efb.WithName("Short Url") @@ -255,7 +255,7 @@ namespace NadekoBot.Modules.Searches var desc = item["text"].ToString(); var types = String.Join(",\n", item["types"].ToObject()); var img = item["editions"][0]["image_url"].ToString(); - var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor) + var embed = new EmbedBuilder().WithOkColor() .WithTitle(item["name"].ToString()) .WithDescription(desc) .WithImage(eib => eib.WithUrl(img)) @@ -364,7 +364,7 @@ namespace NadekoBot.Modules.Searches .WithUrl("http://www.yodaspeak.co.uk/") .WithAuthor(au => au.WithName("Yoda").WithIconUrl("http://www.yodaspeak.co.uk/yoda-small1.gif")) .WithDescription(res) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } catch @@ -405,7 +405,7 @@ namespace NadekoBot.Modules.Searches var word = item["word"].ToString(); var def = item["definition"].ToString(); var link = item["permalink"].ToString(); - var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor) + var embed = new EmbedBuilder().WithOkColor() .WithUrl(link) .WithAuthor(eab => eab.WithIconUrl("http://i.imgur.com/nwERwQE.jpg").WithName(word)) .WithDescription(def); @@ -452,7 +452,7 @@ namespace NadekoBot.Modules.Searches var hashtag = item["hashtag"].ToString(); var link = item["uri"].ToString(); var desc = item["text"].ToString(); - await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) + await channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithAuthor(eab => eab.WithUrl(link) .WithIconUrl("http://res.cloudinary.com/urbandictionary/image/upload/a_exif,c_fit,h_200,w_200/v1394975045/b8oszuu3tbq7ebyo7vo1.jpg") .WithName(query)) diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index 740b18e0..e5ecdb10 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Utility .AddField(fb => fb.WithName("**Region**").WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName("**Roles**").WithValue(guild.Roles.Count().ToString()).WithIsInline(true)) .WithImage(tn => tn.WithUrl(guild.IconUrl)) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); if (guild.Emojis.Count() > 0) { embed.AddField(fb => fb.WithName("**Custom Emojis**").WithValue(Format.Italics(string.Join(", ", guild.Emojis))).WithIsInline(true)); @@ -67,7 +67,7 @@ namespace NadekoBot.Modules.Utility .AddField(fb => fb.WithName("**ID**").WithValue(ch.Id.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Users**").WithValue(usercount.ToString()).WithIsInline(true)) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); await msg.Channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } @@ -92,7 +92,7 @@ namespace NadekoBot.Modules.Utility .AddField(fb => fb.WithName("**Current Game**").WithValue($"{(user.Game?.Name == null ? "-" : user.Game.Name)}").WithIsInline(true)) .AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.Roles.Count()})** - {string.Join(", ", user.Roles.Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true)) .WithThumbnail(tn => tn.WithUrl(user.AvatarUrl)) - .WithColor(NadekoBot.OkColor); + .WithOkColor(); await msg.Channel.EmbedAsync(embed.Build()).ConfigureAwait(false); } } diff --git a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs b/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs index 3468a15f..78874828 100644 --- a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs +++ b/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs @@ -106,7 +106,7 @@ namespace NadekoBot.Modules.Utility { var res = Units.GroupBy(x => x.UnitType) .Aggregate(new EmbedBuilder().WithTitle("__Units which can be used by the converter__") - .WithColor(NadekoBot.OkColor), + .WithOkColor(), (embed, g) => embed.AddField(efb => efb.WithName(g.Key.ToTitleCase()) .WithValue(String.Join(", ", g.Select(x => x.Triggers.FirstOrDefault()) diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 1e6fce9c..40670c27 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -263,7 +263,7 @@ namespace NadekoBot.Modules.Utility return; } - await channel.EmbedAsync(guilds.Aggregate(new EmbedBuilder().WithColor(NadekoBot.OkColor), + await channel.EmbedAsync(guilds.Aggregate(new EmbedBuilder().WithOkColor(), (embed, g) => embed.AddField(efb => efb.WithName(g.Name) .WithValue($"```css\nID: {g.Id}\nMembers: {g.GetUsers().Count}\nOwnerID: {g.OwnerId} ```") .WithIsInline(false))) diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index a94681bb..5cbe62ab 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -25,6 +25,20 @@ namespace NadekoBot.Extensions http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); } + public static EmbedBuilder WithOkColor(this EmbedBuilder eb) => + eb.WithColor(NadekoBot.OkColor); + + public static IMessage DeleteAfter(this IUserMessage msg, int seconds) + { + Task.Run(async () => + { + await Task.Delay(seconds * 1000); + try { await msg.DeleteAsync().ConfigureAwait(false); } + catch { } + }); + return msg; + } + public static async Task SendMessageToOwnerAsync(this IGuild guild, string message) { var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).CreateDMChannelAsync()