From 8f90410e2d80d56c67b7c025c61f8af5a09da294 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Tue, 11 Jul 2017 03:16:56 +0200 Subject: [PATCH] Small refactor --- src/NadekoBot/Modules/Music/Music.cs | 30 +- .../{Music => Impl}/SoundCloudApiService.cs | 2 +- .../Services/Music/Extensions/Extensions.cs | 22 ++ src/NadekoBot/Services/Music/MusicService.cs | 348 +++--------------- .../Services/Music/OldSongResolver.cs | 111 ++++++ src/NadekoBot/Services/Music/SongInfo.cs | 21 +- src/NadekoBot/Services/Music/SongResolver.cs | 111 ------ .../SongResolver/ISongResolverFactory.cs | 15 + .../Music/SongResolver/SongResolverFactory.cs | 41 +++ .../Strategies/IResolverStrategy.cs | 9 + .../Strategies/LocalSongResolveStrategy.cs | 22 ++ .../Strategies/RadioResolveStrategy.cs | 138 +++++++ .../Strategies/SoundCloudResolveStrategy.cs | 27 ++ .../Strategies/YoutubeResolveStrategy.cs | 69 ++++ 14 files changed, 539 insertions(+), 427 deletions(-) rename src/NadekoBot/Services/{Music => Impl}/SoundCloudApiService.cs (99%) create mode 100644 src/NadekoBot/Services/Music/Extensions/Extensions.cs create mode 100644 src/NadekoBot/Services/Music/OldSongResolver.cs delete mode 100644 src/NadekoBot/Services/Music/SongResolver.cs create mode 100644 src/NadekoBot/Services/Music/SongResolver/ISongResolverFactory.cs create mode 100644 src/NadekoBot/Services/Music/SongResolver/SongResolverFactory.cs create mode 100644 src/NadekoBot/Services/Music/SongResolver/Strategies/IResolverStrategy.cs create mode 100644 src/NadekoBot/Services/Music/SongResolver/Strategies/LocalSongResolveStrategy.cs create mode 100644 src/NadekoBot/Services/Music/SongResolver/Strategies/RadioResolveStrategy.cs create mode 100644 src/NadekoBot/Services/Music/SongResolver/Strategies/SoundCloudResolveStrategy.cs create mode 100644 src/NadekoBot/Services/Music/SongResolver/Strategies/YoutubeResolveStrategy.cs diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index f137e6bf..e5605999 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -15,6 +15,8 @@ using System.Collections.Concurrent; using System.IO; using System.Net.Http; using Newtonsoft.Json.Linq; +using NadekoBot.Services.Music.Extensions; +using NadekoBot.Services.Impl; namespace NadekoBot.Modules.Music { @@ -122,12 +124,15 @@ namespace NadekoBot.Modules.Music { try { - var queuedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (index)).WithMusicIcon()) - .WithDescription($"{songInfo.PrettyName}\n{GetText("queue")} ") - .WithThumbnailUrl(songInfo.Thumbnail) - .WithFooter(ef => ef.WithText(songInfo.PrettyProvider))) - .ConfigureAwait(false); + var embed = new EmbedBuilder().WithOkColor() + .WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (index)).WithMusicIcon()) + .WithDescription($"{songInfo.PrettyName}\n{GetText("queue")} ") + .WithFooter(ef => ef.WithText(songInfo.PrettyProvider)); + + if (Uri.IsWellFormedUriString(songInfo.Thumbnail, UriKind.Absolute)) + embed.WithThumbnailUrl(songInfo.Thumbnail); + + var queuedMessage = await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false); if (mp.Stopped) { (await ReplyErrorLocalized("queue_stopped", Format.Code(Prefix + "play")).ConfigureAwait(false)).DeleteAfter(10); @@ -375,6 +380,11 @@ namespace NadekoBot.Modules.Music [Priority(0)] public async Task SongRemove(int index) { + if (index < 1) + { + await ReplyErrorLocalized("removed_song_error").ConfigureAwait(false); + return; + } var mp = await _music.GetOrCreatePlayer(Context); try { @@ -399,7 +409,9 @@ namespace NadekoBot.Modules.Music [Priority(1)] public async Task SongRemove(All all) { - var mp = await _music.GetOrCreatePlayer(Context); + var mp = _music.GetPlayerOrDefault(Context.Guild.Id); + if (mp == null) + return; mp.Stop(true); await ReplyConfirmLocalized("queue_cleared").ConfigureAwait(false); } @@ -586,7 +598,9 @@ namespace NadekoBot.Modules.Music try { await Task.Yield(); - await InternalQueue(mp, await _music.SongInfoFromSVideo(svideo, Context.User.ToString()), true); + var sinfo = await svideo.GetSongInfo(); + sinfo.QueuerName = Context.User.ToString(); + await InternalQueue(mp, sinfo, true); } catch (Exception ex) { diff --git a/src/NadekoBot/Services/Music/SoundCloudApiService.cs b/src/NadekoBot/Services/Impl/SoundCloudApiService.cs similarity index 99% rename from src/NadekoBot/Services/Music/SoundCloudApiService.cs rename to src/NadekoBot/Services/Impl/SoundCloudApiService.cs index 1b2ecac0..a3dc8cdd 100644 --- a/src/NadekoBot/Services/Music/SoundCloudApiService.cs +++ b/src/NadekoBot/Services/Impl/SoundCloudApiService.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; -namespace NadekoBot.Services.Music +namespace NadekoBot.Services.Impl { public class SoundCloudApiService { diff --git a/src/NadekoBot/Services/Music/Extensions/Extensions.cs b/src/NadekoBot/Services/Music/Extensions/Extensions.cs new file mode 100644 index 00000000..4403c2dc --- /dev/null +++ b/src/NadekoBot/Services/Music/Extensions/Extensions.cs @@ -0,0 +1,22 @@ +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using System; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Music.Extensions +{ + public static class Extensions + { + public static Task GetSongInfo(this SoundCloudVideo svideo) => + Task.FromResult(new SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = () => svideo.StreamLink(), + ProviderType = MusicType.Soundcloud, + Query = svideo.TrackLink, + Thumbnail = svideo.artwork_url, + TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) + }); + } +} diff --git a/src/NadekoBot/Services/Music/MusicService.cs b/src/NadekoBot/Services/Music/MusicService.cs index dab5aad1..5d949618 100644 --- a/src/NadekoBot/Services/Music/MusicService.cs +++ b/src/NadekoBot/Services/Music/MusicService.cs @@ -7,15 +7,11 @@ using NadekoBot.Extensions; using NadekoBot.Services.Database.Models; using NLog; using System.IO; -using VideoLibrary; using System.Collections.Generic; using Discord.Commands; using Discord.WebSocket; -using System.Text.RegularExpressions; -using System.Net.Http; +using NadekoBot.Services.Music.SongResolver; using NadekoBot.Services.Impl; -using System.Globalization; -using System.Threading; namespace NadekoBot.Services.Music { @@ -47,7 +43,6 @@ namespace NadekoBot.Services.Music _sc = sc; _creds = creds; _log = LogManager.GetCurrentClassLogger(); - _yt = YouTube.Default; try { Directory.Delete(MusicDataPath, true); } catch { } @@ -199,91 +194,18 @@ namespace NadekoBot.Services.Music { query.ThrowIfNull(nameof(query)); - SongInfo sinfo = null; - switch (musicType) - { - case MusicType.YouTube: - sinfo = await ResolveYoutubeSong(query, queuerName); - break; - case MusicType.Radio: - try { sinfo = ResolveRadioSong(IsRadioLink(query) ? await HandleStreamContainers(query) : query, queuerName); } catch { }; - break; - case MusicType.Local: - sinfo = ResolveLocalSong(query, queuerName); - break; - case MusicType.Soundcloud: - sinfo = await ResolveSoundCloudSong(query, queuerName); - break; - case null: - if (_sc.IsSoundCloudLink(query)) - sinfo = await ResolveSoundCloudSong(query, queuerName); - else if (IsRadioLink(query)) - sinfo = ResolveRadioSong(await HandleStreamContainers(query), queuerName); - else - try - { - sinfo = await ResolveYoutubeSong(query, queuerName); - } - catch - { - sinfo = null; - } - break; - } + ISongResolverFactory resolverFactory = new SongResolverFactory(_sc); + var strategy = await resolverFactory.GetResolveStrategy(query, musicType); + var sinfo = await strategy.ResolveSong(query); + + if (sinfo == null) + return null; + + sinfo.QueuerName = queuerName; return sinfo; } - public async Task ResolveSoundCloudSong(string query, string queuerName) - { - var svideo = !_sc.IsSoundCloudLink(query) ? - await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false) : - await _sc.ResolveVideoAsync(query).ConfigureAwait(false); - - if (svideo == null) - return null; - return await SongInfoFromSVideo(svideo, queuerName); - } - - public Task SongInfoFromSVideo(SoundCloudVideo svideo, string queuerName) => - Task.FromResult(new SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = () => svideo.StreamLink(), - ProviderType = MusicType.Soundcloud, - Query = svideo.TrackLink, - AlbumArt = svideo.artwork_url, - QueuerName = queuerName, - TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) - }); - - public SongInfo ResolveLocalSong(string query, string queuerName) - { - return new SongInfo - { - Uri = () => Task.FromResult("\"" + Path.GetFullPath(query) + "\""), - Title = Path.GetFileNameWithoutExtension(query), - Provider = "Local File", - ProviderType = MusicType.Local, - Query = query, - QueuerName = queuerName - }; - } - - public SongInfo ResolveRadioSong(string query, string queuerName) - { - return new SongInfo - { - Uri = () => Task.FromResult(query), - Title = query, - Provider = "Radio Stream", - ProviderType = MusicType.Radio, - Query = query, - QueuerName = queuerName - }; - } - public async Task DestroyAllPlayers() { foreach (var key in MusicPlayers.Keys) @@ -292,214 +214,66 @@ namespace NadekoBot.Services.Music } } - public Task ResolveYoutubeSong(string query, string queuerName) - { - _log.Info("Getting video"); - //var (link, video) = await GetYoutubeVideo(query); - - //if (video == null) // do something with this error - //{ - // _log.Info("Could not load any video elements based on the query."); - // return null; - //} - ////var m = Regex.Match(query, @"\?t=(?\d*)"); - ////int gotoTime = 0; - ////if (m.Captures.Count > 0) - //// int.TryParse(m.Groups["t"].ToString(), out gotoTime); - - //_log.Info("Creating song info"); - //var song = new SongInfo - //{ - // Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" - // Provider = "YouTube", - // Uri = async () => { - // var vid = await GetYoutubeVideo(query); - // if (vid.Item2 == null) - // throw new HttpRequestException(); - - // return await vid.Item2.GetUriAsync(); - // }, - // Query = link, - // ProviderType = MusicType.YouTube, - // QueuerName = queuerName - //}; - return GetYoutubeVideo(query, queuerName); - } - - private async Task GetYoutubeVideo(string query, string queuerName) - { - _log.Info("Getting link"); - string[] data; - try - { - using (var ytdl = new YtdlOperation()) - { - data = (await ytdl.GetDataAsync(query)).Split('\n'); - } - if (data.Length < 6) - { - _log.Info("No song found. Data less than 6"); - return null; - } - TimeSpan time; - if (!TimeSpan.TryParseExact(data[4], new[] { "ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss" }, CultureInfo.InvariantCulture, out time)) - time = TimeSpan.FromHours(24); - - return new SongInfo() - { - Title = data[0], - VideoId = data[1], - Uri = async () => { - using (var ytdl = new YtdlOperation()) - { - data = (await ytdl.GetDataAsync(query)).Split('\n'); - } - if (data.Length < 6) - { - _log.Info("No song found. Data less than 6"); - return null; - } - return data[2]; - }, - AlbumArt = data[3], - TotalTime = time, - QueuerName = queuerName, - Provider = "YouTube", - ProviderType = MusicType.YouTube, - Query = "https://youtube.com/watch?v=" + data[1], - }; - } - catch (Exception ex) - { - _log.Warn(ex); - return null; - } - - //if (string.IsNullOrWhiteSpace(link)) - //{ - // _log.Info("No song found."); - // return (null, null); - //} - //_log.Info("Getting all videos"); - //var allVideos = await Task.Run(async () => { try { return await _yt.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(); - - //return (link, video); - } - - private bool IsRadioLink(string query) => - (query.StartsWith("http") || - query.StartsWith("ww")) - && - (query.Contains(".pls") || - query.Contains(".m3u") || - query.Contains(".asx") || - query.Contains(".xspf")); - public async Task DestroyPlayer(ulong id) { if (MusicPlayers.TryRemove(id, out var mp)) await mp.Destroy(); } - private readonly Regex plsRegex = new Regex("File1=(?.*?)\\n", RegexOptions.Compiled); - private readonly Regex m3uRegex = new Regex("(?^[^#].*)", RegexOptions.Compiled | RegexOptions.Multiline); - private readonly Regex asxRegex = new Regex(".*?)\"", RegexOptions.Compiled); - private readonly Regex xspfRegex = new Regex("(?.*?)", RegexOptions.Compiled); - private readonly YouTube _yt; - private readonly Timer _t; - private 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 = plsRegex.Match(file); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - _log.Warn($"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 = m3uRegex.Match(file); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - _log.Warn($"Failed reading .m3u:\n{file}"); - return null; - } - } - if (query.Contains(".asx")) - { - // - try - { - var m = asxRegex.Match(file); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - _log.Warn($"Failed reading .asx:\n{file}"); - return null; - } - } - if (query.Contains(".xspf")) - { - /* - - - - file:///mp3s/song_1.mp3 - */ - try - { - var m = xspfRegex.Match(file); - var res = m.Groups["url"]?.ToString(); - return res?.Trim(); - } - catch - { - _log.Warn($"Failed reading .xspf:\n{file}"); - return null; - } - } + //public Task ResolveYoutubeSong(string query, string queuerName) + //{ + // _log.Info("Getting video"); + // //var (link, video) = await GetYoutubeVideo(query); - return query; - } + // //if (video == null) // do something with this error + // //{ + // // _log.Info("Could not load any video elements based on the query."); + // // return null; + // //} + // ////var m = Regex.Match(query, @"\?t=(?\d*)"); + // ////int gotoTime = 0; + // ////if (m.Captures.Count > 0) + // //// int.TryParse(m.Groups["t"].ToString(), out gotoTime); + + // //_log.Info("Creating song info"); + // //var song = new SongInfo + // //{ + // // Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" + // // Provider = "YouTube", + // // Uri = async () => { + // // var vid = await GetYoutubeVideo(query); + // // if (vid.Item2 == null) + // // throw new HttpRequestException(); + + // // return await vid.Item2.GetUriAsync(); + // // }, + // // Query = link, + // // ProviderType = MusicType.YouTube, + // // QueuerName = queuerName + // //}; + // return GetYoutubeVideo(query, queuerName); + //} + + //private async Task GetYoutubeVideo(string query, string queuerName) + //{ + + + // //if (string.IsNullOrWhiteSpace(link)) + // //{ + // // _log.Info("No song found."); + // // return (null, null); + // //} + // //_log.Info("Getting all videos"); + // //var allVideos = await Task.Run(async () => { try { return await _yt.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(); + + // //return (link, video); + //} } } \ No newline at end of file diff --git a/src/NadekoBot/Services/Music/OldSongResolver.cs b/src/NadekoBot/Services/Music/OldSongResolver.cs new file mode 100644 index 00000000..147bb6e5 --- /dev/null +++ b/src/NadekoBot/Services/Music/OldSongResolver.cs @@ -0,0 +1,111 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//namespace NadekoBot.Services.Music +//{ +// public class OldSongResolver +// { +// // public 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 (_sc.IsSoundCloudLink(query)) +// // { +// // var svideo = await _sc.ResolveVideoAsync(query).ConfigureAwait(false); +// // return new Song(new SongInfo +// // { +// // Title = svideo.FullName, +// // Provider = "SoundCloud", +// // Uri = await svideo.StreamLink(), +// // ProviderType = musicType, +// // Query = svideo.TrackLink, +// // AlbumArt = svideo.artwork_url, +// // }) +// // { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; +// // } + +// // if (musicType == MusicType.Soundcloud) +// // { +// // var svideo = await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false); +// // return new Song(new SongInfo +// // { +// // Title = svideo.FullName, +// // Provider = "SoundCloud", +// // Uri = await svideo.StreamLink(), +// // ProviderType = MusicType.Soundcloud, +// // Query = svideo.TrackLink, +// // AlbumArt = svideo.artwork_url, +// // }) +// // { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; +// // } + +// // var link = (await _google.GetVideoLinksByKeywordAsync(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 = await video.GetUriAsync().ConfigureAwait(false), +// // Query = link, +// // ProviderType = musicType, +// // }); +// // song.SkipTo = gotoTime; +// // return song; +// // } +// // catch (Exception ex) +// // { +// // _log.Warn($"Failed resolving the link.{ex.Message}"); +// // _log.Warn(ex); +// // return null; +// // } +// // } +// } +//} diff --git a/src/NadekoBot/Services/Music/SongInfo.cs b/src/NadekoBot/Services/Music/SongInfo.cs index cb3798d9..db4a65e7 100644 --- a/src/NadekoBot/Services/Music/SongInfo.cs +++ b/src/NadekoBot/Services/Music/SongInfo.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Services.Music public string Query { get; set; } public string Title { get; set; } public Func> Uri { get; set; } - public string AlbumArt { get; set; } + public string Thumbnail { get; set; } public string QueuerName { get; set; } public TimeSpan TotalTime { get; set; } = TimeSpan.Zero; @@ -75,24 +75,5 @@ namespace NadekoBot.Services.Music } private readonly Regex videoIdRegex = new Regex("<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+", RegexOptions.Compiled); - public string Thumbnail - { - get - { - switch (ProviderType) - { - case MusicType.Radio: - return "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links - case MusicType.YouTube: - return $"https://img.youtube.com/vi/{ VideoId }/0.jpg"; - case MusicType.Local: - return "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links - case MusicType.Soundcloud: - return AlbumArt; - default: - return ""; - } - } - } } } diff --git a/src/NadekoBot/Services/Music/SongResolver.cs b/src/NadekoBot/Services/Music/SongResolver.cs deleted file mode 100644 index 06fe5194..00000000 --- a/src/NadekoBot/Services/Music/SongResolver.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Services.Music -{ - public class SongResolver - { - // public 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 (_sc.IsSoundCloudLink(query)) - // { - // var svideo = await _sc.ResolveVideoAsync(query).ConfigureAwait(false); - // return new Song(new SongInfo - // { - // Title = svideo.FullName, - // Provider = "SoundCloud", - // Uri = await svideo.StreamLink(), - // ProviderType = musicType, - // Query = svideo.TrackLink, - // AlbumArt = svideo.artwork_url, - // }) - // { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; - // } - - // if (musicType == MusicType.Soundcloud) - // { - // var svideo = await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false); - // return new Song(new SongInfo - // { - // Title = svideo.FullName, - // Provider = "SoundCloud", - // Uri = await svideo.StreamLink(), - // ProviderType = MusicType.Soundcloud, - // Query = svideo.TrackLink, - // AlbumArt = svideo.artwork_url, - // }) - // { TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) }; - // } - - // var link = (await _google.GetVideoLinksByKeywordAsync(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 = await video.GetUriAsync().ConfigureAwait(false), - // Query = link, - // ProviderType = musicType, - // }); - // song.SkipTo = gotoTime; - // return song; - // } - // catch (Exception ex) - // { - // _log.Warn($"Failed resolving the link.{ex.Message}"); - // _log.Warn(ex); - // return null; - // } - // } - } -} diff --git a/src/NadekoBot/Services/Music/SongResolver/ISongResolverFactory.cs b/src/NadekoBot/Services/Music/SongResolver/ISongResolverFactory.cs new file mode 100644 index 00000000..b2ae8aa8 --- /dev/null +++ b/src/NadekoBot/Services/Music/SongResolver/ISongResolverFactory.cs @@ -0,0 +1,15 @@ +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Music.SongResolver.Strategies; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Music.SongResolver +{ + public interface ISongResolverFactory + { + Task GetResolveStrategy(string query, MusicType? musicType); + } +} diff --git a/src/NadekoBot/Services/Music/SongResolver/SongResolverFactory.cs b/src/NadekoBot/Services/Music/SongResolver/SongResolverFactory.cs new file mode 100644 index 00000000..a70102f1 --- /dev/null +++ b/src/NadekoBot/Services/Music/SongResolver/SongResolverFactory.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Music.SongResolver.Strategies; +using NadekoBot.Services.Impl; + +namespace NadekoBot.Services.Music.SongResolver +{ + public class SongResolverFactory : ISongResolverFactory + { + private readonly SoundCloudApiService _sc; + + public SongResolverFactory(SoundCloudApiService sc) + { + _sc = sc; + } + + public async Task GetResolveStrategy(string query, MusicType? musicType) + { + await Task.Yield(); //for async warning + switch (musicType) + { + case MusicType.YouTube: + return new YoutubeResolveStrategy(); + case MusicType.Radio: + return new RadioResolveStrategy(); + case MusicType.Local: + return new LocalSongResolveStrategy(); + case MusicType.Soundcloud: + return new SoundcloudResolveStrategy(_sc); + default: + if (_sc.IsSoundCloudLink(query)) + return new SoundcloudResolveStrategy(_sc); + else if (RadioResolveStrategy.IsRadioLink(query)) + return new RadioResolveStrategy(); + // maybe add a check for local files in the future + else + return new YoutubeResolveStrategy(); + } + } + } +} diff --git a/src/NadekoBot/Services/Music/SongResolver/Strategies/IResolverStrategy.cs b/src/NadekoBot/Services/Music/SongResolver/Strategies/IResolverStrategy.cs new file mode 100644 index 00000000..42a77637 --- /dev/null +++ b/src/NadekoBot/Services/Music/SongResolver/Strategies/IResolverStrategy.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace NadekoBot.Services.Music.SongResolver.Strategies +{ + public interface IResolveStrategy + { + Task ResolveSong(string query); + } +} diff --git a/src/NadekoBot/Services/Music/SongResolver/Strategies/LocalSongResolveStrategy.cs b/src/NadekoBot/Services/Music/SongResolver/Strategies/LocalSongResolveStrategy.cs new file mode 100644 index 00000000..bac28fd8 --- /dev/null +++ b/src/NadekoBot/Services/Music/SongResolver/Strategies/LocalSongResolveStrategy.cs @@ -0,0 +1,22 @@ +using NadekoBot.Services.Database.Models; +using System.IO; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Music.SongResolver.Strategies +{ + public class LocalSongResolveStrategy : IResolveStrategy + { + public Task ResolveSong(string query) + { + return Task.FromResult(new SongInfo + { + Uri = () => Task.FromResult("\"" + Path.GetFullPath(query) + "\""), + Title = Path.GetFileNameWithoutExtension(query), + Provider = "Local File", + ProviderType = MusicType.Local, + Query = query, + Thumbnail = "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png", + }); + } + } +} diff --git a/src/NadekoBot/Services/Music/SongResolver/Strategies/RadioResolveStrategy.cs b/src/NadekoBot/Services/Music/SongResolver/Strategies/RadioResolveStrategy.cs new file mode 100644 index 00000000..212db609 --- /dev/null +++ b/src/NadekoBot/Services/Music/SongResolver/Strategies/RadioResolveStrategy.cs @@ -0,0 +1,138 @@ +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Music.SongResolver.Strategies +{ + public class RadioResolveStrategy : IResolveStrategy + { + private readonly Regex plsRegex = new Regex("File1=(?.*?)\\n", RegexOptions.Compiled); + private readonly Regex m3uRegex = new Regex("(?^[^#].*)", RegexOptions.Compiled | RegexOptions.Multiline); + private readonly Regex asxRegex = new Regex(".*?)\"", RegexOptions.Compiled); + private readonly Regex xspfRegex = new Regex("(?.*?)", RegexOptions.Compiled); + private readonly Logger _log; + + public RadioResolveStrategy() + { + _log = LogManager.GetCurrentClassLogger(); + } + + public async Task ResolveSong(string query) + { + if (IsRadioLink(query)) + query = await HandleStreamContainers(query); + + return new SongInfo + { + Uri = () => Task.FromResult(query), + Title = query, + Provider = "Radio Stream", + ProviderType = MusicType.Radio, + Query = query, + TotalTime = TimeSpan.MaxValue, + Thumbnail = "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png", + }; + } + + public static bool IsRadioLink(string query) => + (query.StartsWith("http") || + query.StartsWith("ww")) + && + (query.Contains(".pls") || + query.Contains(".m3u") || + query.Contains(".asx") || + query.Contains(".xspf")); + + private 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 = plsRegex.Match(file); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + _log.Warn($"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 = m3uRegex.Match(file); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + _log.Warn($"Failed reading .m3u:\n{file}"); + return null; + } + + } + if (query.Contains(".asx")) + { + // + try + { + var m = asxRegex.Match(file); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + _log.Warn($"Failed reading .asx:\n{file}"); + return null; + } + } + if (query.Contains(".xspf")) + { + /* + + + + file:///mp3s/song_1.mp3 + */ + try + { + var m = xspfRegex.Match(file); + var res = m.Groups["url"]?.ToString(); + return res?.Trim(); + } + catch + { + _log.Warn($"Failed reading .xspf:\n{file}"); + return null; + } + } + + return query; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Music/SongResolver/Strategies/SoundCloudResolveStrategy.cs b/src/NadekoBot/Services/Music/SongResolver/Strategies/SoundCloudResolveStrategy.cs new file mode 100644 index 00000000..3e2b9269 --- /dev/null +++ b/src/NadekoBot/Services/Music/SongResolver/Strategies/SoundCloudResolveStrategy.cs @@ -0,0 +1,27 @@ +using NadekoBot.Services.Impl; +using NadekoBot.Services.Music.Extensions; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Music.SongResolver.Strategies +{ + public class SoundcloudResolveStrategy : IResolveStrategy + { + private readonly SoundCloudApiService _sc; + + public SoundcloudResolveStrategy(SoundCloudApiService sc) + { + _sc = sc; + } + + public async Task ResolveSong(string query) + { + var svideo = !_sc.IsSoundCloudLink(query) ? + await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false) : + await _sc.ResolveVideoAsync(query).ConfigureAwait(false); + + if (svideo == null) + return null; + return await svideo.GetSongInfo(); + } + } +} diff --git a/src/NadekoBot/Services/Music/SongResolver/Strategies/YoutubeResolveStrategy.cs b/src/NadekoBot/Services/Music/SongResolver/Strategies/YoutubeResolveStrategy.cs new file mode 100644 index 00000000..b183e9d4 --- /dev/null +++ b/src/NadekoBot/Services/Music/SongResolver/Strategies/YoutubeResolveStrategy.cs @@ -0,0 +1,69 @@ +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; +using System; +using System.Globalization; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Music.SongResolver.Strategies +{ + public class YoutubeResolveStrategy : IResolveStrategy + { + private readonly Logger _log; + + public YoutubeResolveStrategy() + { + _log = LogManager.GetCurrentClassLogger(); + } + + public async Task ResolveSong(string query) + { + _log.Info("Getting link"); + string[] data; + try + { + using (var ytdl = new YtdlOperation()) + { + data = (await ytdl.GetDataAsync(query)).Split('\n'); + } + if (data.Length < 6) + { + _log.Info("No song found. Data less than 6"); + return null; + } + TimeSpan time; + if (!TimeSpan.TryParseExact(data[4], new[] { "ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss" }, CultureInfo.InvariantCulture, out time)) + time = TimeSpan.FromHours(24); + + return new SongInfo() + { + Title = data[0], + VideoId = data[1], + Uri = async () => + { + using (var ytdl = new YtdlOperation()) + { + data = (await ytdl.GetDataAsync(query)).Split('\n'); + } + if (data.Length < 6) + { + _log.Info("No song found. Data less than 6"); + return null; + } + return data[2]; + }, + Thumbnail = data[3], + TotalTime = time, + Provider = "YouTube", + ProviderType = MusicType.YouTube, + Query = "https://youtube.com/watch?v=" + data[1], + }; + } + catch (Exception ex) + { + _log.Warn(ex); + return null; + } + } + } +}