adding spotify integration
This commit is contained in:
		
							
								
								
									
										112
									
								
								Kasbot.APP/Services/Internal/MediaService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								Kasbot.APP/Services/Internal/MediaService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
using Discord.Rest;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using Kasbot.Models;
 | 
			
		||||
using Kasbot.Services.Internal;
 | 
			
		||||
using Serilog;
 | 
			
		||||
using YoutubeExplode.Videos;
 | 
			
		||||
 | 
			
		||||
namespace Kasbot.App.Services.Internal
 | 
			
		||||
{
 | 
			
		||||
    public class MediaService
 | 
			
		||||
    {
 | 
			
		||||
        private YoutubeService YoutubeService { get; set; }
 | 
			
		||||
        private SpotifyService SpotifyService { get; set; }
 | 
			
		||||
        private ILogger Logger { get; set; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public MediaService(YoutubeService youtubeService, SpotifyService spotifyService, ILogger logger)
 | 
			
		||||
        {
 | 
			
		||||
            this.YoutubeService = youtubeService;
 | 
			
		||||
            this.SpotifyService = spotifyService;
 | 
			
		||||
            this.Logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Media> FetchSingleMedia(Media media, SearchType mediaType)
 | 
			
		||||
        {
 | 
			
		||||
            if (mediaType == SearchType.SpotifyTrack)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Debug($"Fetching single media: {media.Search}");
 | 
			
		||||
                media = await SpotifyService.FetchSingleMedia(media);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Logger.Debug($"Fetching single media (YouTube): {media.Search}");
 | 
			
		||||
            media = await YoutubeService.FetchSingleMedia(media);
 | 
			
		||||
 | 
			
		||||
            if (media.VideoId == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Error($"No video found for \"{media.Search}\".");
 | 
			
		||||
                throw new Exception($"No video found for \"{media.Search}\".");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return media;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<MediaCollection> FetchMediaCollection(Media rawMedia, SearchType mediaType)
 | 
			
		||||
        {
 | 
			
		||||
            var collection = new MediaCollection();
 | 
			
		||||
 | 
			
		||||
            if (mediaType == SearchType.SpotifyPlaylist)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Debug($"Fetching playlist from Spotify: {rawMedia.Search}");
 | 
			
		||||
                collection = await SpotifyService.FetchPlaylist(rawMedia);
 | 
			
		||||
                var tasks = collection.Medias.ToList().Select(media => YoutubeService.FetchSingleMedia(media));
 | 
			
		||||
                var results = await Task.WhenAll(tasks);
 | 
			
		||||
                Logger.Debug($"Fetched playlist from Spotify: {rawMedia.Search}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (mediaType == SearchType.SpotifyAlbum)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Debug($"Fetching album from Spotify: {rawMedia.Search}");
 | 
			
		||||
                collection = await SpotifyService.FetchAlbum(rawMedia);
 | 
			
		||||
                var tasks = collection.Medias.ToList().Select(media => YoutubeService.FetchSingleMedia(media));
 | 
			
		||||
                var results = await Task.WhenAll(tasks);
 | 
			
		||||
                Logger.Debug($"Fetched album from Spotify: {rawMedia.Search}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (mediaType == SearchType.YoutubePlaylist)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Debug($"Fetching playlist from YouTube: {rawMedia.Search}");
 | 
			
		||||
                collection = await YoutubeService.FetchPlaylist(rawMedia);
 | 
			
		||||
                Logger.Debug($"Fetched playlist from YouTube: {rawMedia.Search}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return collection;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<MemoryStream?> DownloadAudioFromYoutube(Media media)
 | 
			
		||||
        {
 | 
			
		||||
            return await YoutubeService.DownloadAudioFromYoutube(media);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class Media
 | 
			
		||||
    {
 | 
			
		||||
        public string Search { get; set; }
 | 
			
		||||
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public TimeSpan Length { get; set; }
 | 
			
		||||
        public Flags Flags { get; set; }
 | 
			
		||||
 | 
			
		||||
        public VideoId? VideoId { get; set; }
 | 
			
		||||
        public RestUserMessage PlayMessage { get; set; }
 | 
			
		||||
        public RestUserMessage? QueueMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        private SocketUserMessage message;
 | 
			
		||||
        public SocketUserMessage Message
 | 
			
		||||
        {
 | 
			
		||||
            get => message;
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                message = value;
 | 
			
		||||
                Channel = value.Channel;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        public ISocketMessageChannel Channel { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class MediaCollection
 | 
			
		||||
    {
 | 
			
		||||
        public string CollectionName { get; set; }
 | 
			
		||||
        public List<Media> Medias { get; set; } = new List<Media>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										146
									
								
								Kasbot.APP/Services/Internal/SpotifyService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								Kasbot.APP/Services/Internal/SpotifyService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
using Serilog;
 | 
			
		||||
using SpotifyAPI.Web;
 | 
			
		||||
 | 
			
		||||
namespace Kasbot.App.Services.Internal
 | 
			
		||||
{
 | 
			
		||||
    public class SpotifyService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly SpotifyClient? spotifyClient = null;
 | 
			
		||||
        private ILogger Logger { get; set; }
 | 
			
		||||
 | 
			
		||||
        public SpotifyService(ILogger logger)
 | 
			
		||||
        {
 | 
			
		||||
            this.Logger = logger;
 | 
			
		||||
 | 
			
		||||
            this.spotifyClient = SetupSpotifyClient();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private SpotifyClient SetupSpotifyClient()
 | 
			
		||||
        {
 | 
			
		||||
            var spotifyClientId = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID");
 | 
			
		||||
            var spotifyClientSecret = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_SECRET");
 | 
			
		||||
 | 
			
		||||
            if (spotifyClientId == null || spotifyClientSecret == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Warning("Spotify Token was not found. Will disable Spotify integration.");
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var config = SpotifyClientConfig.CreateDefault();
 | 
			
		||||
 | 
			
		||||
            var request = new ClientCredentialsRequest(spotifyClientId, spotifyClientSecret);
 | 
			
		||||
            var response = new OAuthClient(config).RequestToken(request).Result;
 | 
			
		||||
 | 
			
		||||
            return new SpotifyClient(config.WithToken(response.AccessToken));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Media> FetchSingleMedia(Media media)
 | 
			
		||||
        {
 | 
			
		||||
            if (spotifyClient == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Warning("Spotify integration is disabled.");
 | 
			
		||||
                throw new Exception("Spotify integration is disabled.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var trackId = UrlResolver.GetSpotifyResourceId(media.Search);
 | 
			
		||||
            var spotifyTrack = await spotifyClient.Tracks.Get(trackId);
 | 
			
		||||
 | 
			
		||||
            if (spotifyTrack == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Error($"No track found on Spotify for \"{media.Search}\".");    
 | 
			
		||||
                throw new Exception($"No track found on Spotify for \"{media.Search}\".");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            media.Search = spotifyTrack.Name;
 | 
			
		||||
 | 
			
		||||
            return media;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<MediaCollection> FetchPlaylist(Media rawMedia)
 | 
			
		||||
        {
 | 
			
		||||
            if (spotifyClient == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Warning("Spotify integration is disabled.");
 | 
			
		||||
                throw new Exception("Spotify integration is disabled.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var playlistId = UrlResolver.GetSpotifyResourceId(rawMedia.Search);
 | 
			
		||||
            var spotifyPlaylist = await spotifyClient.Playlists.Get(playlistId);
 | 
			
		||||
 | 
			
		||||
            if (spotifyPlaylist == null || spotifyPlaylist.Tracks == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Error($"No playlist found on Spotify for \"{rawMedia.Search}\".");
 | 
			
		||||
                throw new Exception($"No playlist found on Spotify for \"{rawMedia.Search}\".");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var collection = new MediaCollection();
 | 
			
		||||
            collection.CollectionName = spotifyPlaylist.Name ?? string.Empty;
 | 
			
		||||
 | 
			
		||||
            if (spotifyPlaylist.Tracks.Items == null || spotifyPlaylist.Tracks.Items.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Error($"No tracks found for playlist \"{spotifyPlaylist.Name}\".");
 | 
			
		||||
                throw new Exception($"No tracks found for playlist \"{spotifyPlaylist.Name}\".");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Logger.Debug($"Found {spotifyPlaylist.Tracks.Items.Count} tracks for playlist \"{spotifyPlaylist.Name}\".");
 | 
			
		||||
 | 
			
		||||
            foreach (var playlistTrack in spotifyPlaylist.Tracks.Items)
 | 
			
		||||
            {
 | 
			
		||||
                if (playlistTrack.Track is not FullTrack track)
 | 
			
		||||
                {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                collection.Medias.Add(new Media
 | 
			
		||||
                {
 | 
			
		||||
                    Search = track.Name,
 | 
			
		||||
                    Message = rawMedia.Message,
 | 
			
		||||
                    Flags = rawMedia.Flags,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return collection;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<MediaCollection> FetchAlbum(Media rawMedia)
 | 
			
		||||
        {
 | 
			
		||||
            if (spotifyClient == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Warning("Spotify integration is disabled.");
 | 
			
		||||
                throw new Exception("Spotify integration is disabled.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var albumId = UrlResolver.GetSpotifyResourceId(rawMedia.Search);
 | 
			
		||||
            var spotifyAlbum = await spotifyClient.Albums.Get(albumId);
 | 
			
		||||
 | 
			
		||||
            if (spotifyAlbum == null || spotifyAlbum.Tracks == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Error($"No album found on Spotify for \"{rawMedia.Search}\".");
 | 
			
		||||
                throw new Exception($"No album found on Spotify for \"{rawMedia.Search}\".");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var collection = new MediaCollection();
 | 
			
		||||
            collection.CollectionName = spotifyAlbum.Name ?? string.Empty;
 | 
			
		||||
 | 
			
		||||
            if (spotifyAlbum.Tracks.Items == null || spotifyAlbum.Tracks.Items.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Error($"No tracks found for album \"{spotifyAlbum.Name}\".");
 | 
			
		||||
                throw new Exception($"No tracks found for album \"{spotifyAlbum.Name}\".");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Logger.Debug($"Found {spotifyAlbum.Tracks.Items.Count} tracks for album \"{spotifyAlbum.Name}\".");
 | 
			
		||||
 | 
			
		||||
            foreach (var track in spotifyAlbum.Tracks.Items)
 | 
			
		||||
            {
 | 
			
		||||
                collection.Medias.Add(new Media
 | 
			
		||||
                {
 | 
			
		||||
                    Search = track.Name,
 | 
			
		||||
                    Message = rawMedia.Message,
 | 
			
		||||
                    Flags = rawMedia.Flags,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return collection;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								Kasbot.APP/Services/Internal/UrlResolver.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								Kasbot.APP/Services/Internal/UrlResolver.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
using System.Web;
 | 
			
		||||
 | 
			
		||||
namespace Kasbot.App.Services.Internal
 | 
			
		||||
{
 | 
			
		||||
    public static class UrlResolver
 | 
			
		||||
    {
 | 
			
		||||
        private const string SpotifyUrl = "open.spotify.com";
 | 
			
		||||
 | 
			
		||||
        public static SearchType GetSearchType(string query)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(query))
 | 
			
		||||
                return SearchType.None;
 | 
			
		||||
 | 
			
		||||
            if (IsURL(query))
 | 
			
		||||
            {
 | 
			
		||||
                if (IsSpotifyUrl(query))
 | 
			
		||||
                {
 | 
			
		||||
                    if (query.Contains("/track/"))
 | 
			
		||||
                        return SearchType.SpotifyTrack;
 | 
			
		||||
 | 
			
		||||
                    if (query.Contains("/album/"))
 | 
			
		||||
                        return SearchType.SpotifyAlbum;
 | 
			
		||||
 | 
			
		||||
                    if (query.Contains("/playlist/"))
 | 
			
		||||
                        return SearchType.SpotifyPlaylist;
 | 
			
		||||
 | 
			
		||||
                    if (query.Contains("/artist/"))
 | 
			
		||||
                        return SearchType.SpotifyArtist;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (query.Contains("playlist?list="))
 | 
			
		||||
                    return SearchType.YoutubePlaylist;
 | 
			
		||||
 | 
			
		||||
                if (query.Contains("list="))
 | 
			
		||||
                    return SearchType.VideoPlaylistURL;
 | 
			
		||||
 | 
			
		||||
                return SearchType.VideoURL;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return SearchType.StringSearch;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetVideoId(string url)
 | 
			
		||||
        {
 | 
			
		||||
            if (url.Contains("v="))
 | 
			
		||||
            {
 | 
			
		||||
                var uri = new Uri(url);
 | 
			
		||||
                var query = HttpUtility.ParseQueryString(uri.Query);
 | 
			
		||||
                return query["v"] ?? string.Empty;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (url.Contains("youtu.be/"))
 | 
			
		||||
            {
 | 
			
		||||
                var uri = new Uri(url);
 | 
			
		||||
                return uri.Segments[1];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return url;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetSpotifyResourceId(string url)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(url))
 | 
			
		||||
                return string.Empty;
 | 
			
		||||
 | 
			
		||||
            var uri = new Uri(url);
 | 
			
		||||
            return uri.Segments[uri.Segments.Length - 1];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool IsURL(string url)
 | 
			
		||||
        {
 | 
			
		||||
            return url.StartsWith("http://") || url.StartsWith("https://");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool IsSpotifyUrl(string url)
 | 
			
		||||
        {
 | 
			
		||||
            return url.Contains(SpotifyUrl);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum SearchType
 | 
			
		||||
    {
 | 
			
		||||
        None,
 | 
			
		||||
        StringSearch,
 | 
			
		||||
        VideoURL,
 | 
			
		||||
        VideoPlaylistURL,
 | 
			
		||||
        YoutubePlaylist,
 | 
			
		||||
        SpotifyTrack,
 | 
			
		||||
        SpotifyAlbum,
 | 
			
		||||
        SpotifyPlaylist,
 | 
			
		||||
        SpotifyArtist
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +1,37 @@
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using YoutubeExplode.Videos;
 | 
			
		||||
using Kasbot.App.Services.Internal;
 | 
			
		||||
using Serilog;
 | 
			
		||||
using YoutubeExplode;
 | 
			
		||||
using Discord.Rest;
 | 
			
		||||
using YoutubeExplode.Videos;
 | 
			
		||||
using YoutubeExplode.Videos.Streams;
 | 
			
		||||
using Kasbot.Models;
 | 
			
		||||
 | 
			
		||||
namespace Kasbot.Services.Internal
 | 
			
		||||
{
 | 
			
		||||
    public class YoutubeService
 | 
			
		||||
    {
 | 
			
		||||
        public YoutubeService()
 | 
			
		||||
        {
 | 
			
		||||
        private ILogger Logger { get; set; }
 | 
			
		||||
 | 
			
		||||
        public YoutubeService(ILogger logger)
 | 
			
		||||
        {
 | 
			
		||||
            this.Logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<MediaCollection> DownloadPlaylistMetadataFromYoutube(SocketUserMessage message, string search)
 | 
			
		||||
        public async Task<MediaCollection> FetchPlaylist(Media rawMedia)
 | 
			
		||||
        {
 | 
			
		||||
            var collection = new MediaCollection();
 | 
			
		||||
            var youtube = new YoutubeClient();
 | 
			
		||||
 | 
			
		||||
            var playlistInfo = await youtube.Playlists.GetAsync(search);
 | 
			
		||||
            await youtube.Playlists.GetVideosAsync(search).ForEachAsync(videoId =>
 | 
			
		||||
            Logger.Debug($"Fetching playlist from YouTube: {rawMedia.Search}");
 | 
			
		||||
 | 
			
		||||
            var playlistInfo = await youtube.Playlists.GetAsync(rawMedia.Search);
 | 
			
		||||
            await youtube.Playlists.GetVideosAsync(rawMedia.Search).ForEachAsync(videoId =>
 | 
			
		||||
            {
 | 
			
		||||
                var media = new Media
 | 
			
		||||
                {
 | 
			
		||||
                    Name = videoId.Title,
 | 
			
		||||
                    Length = videoId.Duration ?? new TimeSpan(0),
 | 
			
		||||
                    VideoId = videoId.Id,
 | 
			
		||||
                    Message = message,
 | 
			
		||||
                    Flags = new Flags()
 | 
			
		||||
                    Message = rawMedia.Message,
 | 
			
		||||
                    Flags = rawMedia.Flags
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                collection.Medias.Add(media);
 | 
			
		||||
@@ -36,11 +39,15 @@ namespace Kasbot.Services.Internal
 | 
			
		||||
 | 
			
		||||
            collection.CollectionName = playlistInfo.Title;
 | 
			
		||||
 | 
			
		||||
            Logger.Debug($"Fetched playlist from YouTube: {rawMedia.Search}");
 | 
			
		||||
 | 
			
		||||
            return collection;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Media> DownloadMetadataFromYoutube(Media media)
 | 
			
		||||
        public async Task<Media> FetchSingleMedia(Media media)
 | 
			
		||||
        {
 | 
			
		||||
            Logger.Debug($"Fetching single media: {media.Search}");
 | 
			
		||||
 | 
			
		||||
            var youtube = new YoutubeClient();
 | 
			
		||||
 | 
			
		||||
            IVideo? videoId;
 | 
			
		||||
@@ -52,9 +59,12 @@ namespace Kasbot.Services.Internal
 | 
			
		||||
 | 
			
		||||
            if (videoId == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Error($"No video found for \"{media.Search}\".");
 | 
			
		||||
                return media;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Logger.Debug($"Found video: {videoId.Title}");
 | 
			
		||||
 | 
			
		||||
            media.Name = videoId.Title;
 | 
			
		||||
            media.Length = videoId.Duration ?? new TimeSpan(0);
 | 
			
		||||
            media.VideoId = videoId.Id;
 | 
			
		||||
@@ -82,63 +92,5 @@ namespace Kasbot.Services.Internal
 | 
			
		||||
 | 
			
		||||
            return memoryStream;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public SearchType GetSearchType(string query)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(query))
 | 
			
		||||
                return SearchType.None;
 | 
			
		||||
 | 
			
		||||
            if (query.StartsWith("http://") || query.StartsWith("https://"))
 | 
			
		||||
            {
 | 
			
		||||
                if (query.Contains("playlist?list="))
 | 
			
		||||
                    return SearchType.PlaylistURL;
 | 
			
		||||
 | 
			
		||||
                // need to add 'else if' for ChannelURL
 | 
			
		||||
 | 
			
		||||
                return SearchType.VideoURL;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return SearchType.StringSearch;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum SearchType
 | 
			
		||||
    {
 | 
			
		||||
        None,
 | 
			
		||||
        StringSearch,
 | 
			
		||||
        VideoURL,
 | 
			
		||||
        PlaylistURL,
 | 
			
		||||
        ChannelURL
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class Media
 | 
			
		||||
    {
 | 
			
		||||
        public string Search { get; set; }
 | 
			
		||||
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public TimeSpan Length { get; set; }
 | 
			
		||||
        public Flags Flags { get; set; }
 | 
			
		||||
 | 
			
		||||
        public VideoId? VideoId { get; set; }
 | 
			
		||||
        public RestUserMessage PlayMessage { get; set; }
 | 
			
		||||
        public RestUserMessage? QueueMessage { get; set; }
 | 
			
		||||
 | 
			
		||||
        private SocketUserMessage message;
 | 
			
		||||
        public SocketUserMessage Message
 | 
			
		||||
        {
 | 
			
		||||
            get => message;
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                message = value;
 | 
			
		||||
                Channel = value.Channel;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        public ISocketMessageChannel Channel { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class MediaCollection
 | 
			
		||||
    {
 | 
			
		||||
        public string CollectionName { get; set; }
 | 
			
		||||
        public List<Media> Medias { get; set; } = new List<Media>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,28 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Audio;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Kasbot.App.Services.Internal;
 | 
			
		||||
using Kasbot.Extensions;
 | 
			
		||||
using Kasbot.Models;
 | 
			
		||||
using Kasbot.Services.Internal;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Kasbot.Services
 | 
			
		||||
{
 | 
			
		||||
    public class PlayerService
 | 
			
		||||
    {
 | 
			
		||||
        public Dictionary<ulong, Connection> Clients { get; set; }
 | 
			
		||||
        public YoutubeService YoutubeService { get; set; }
 | 
			
		||||
        public AudioService AudioService { get; set; }
 | 
			
		||||
        private Dictionary<ulong, Connection> Clients { get; set; }
 | 
			
		||||
        private AudioService AudioService { get; set; }
 | 
			
		||||
        private MediaService MediaService { get; set; }
 | 
			
		||||
        private ILogger Logger { get; set; }
 | 
			
		||||
 | 
			
		||||
        public PlayerService(YoutubeService youtubeService, AudioService audioService)
 | 
			
		||||
        public PlayerService(AudioService audioService, MediaService mediaService, ILogger logger)
 | 
			
		||||
        {
 | 
			
		||||
            YoutubeService = youtubeService;
 | 
			
		||||
            AudioService = audioService;
 | 
			
		||||
 | 
			
		||||
            Clients = new Dictionary<ulong, Connection>();
 | 
			
		||||
 | 
			
		||||
            AudioService = audioService;
 | 
			
		||||
            MediaService = mediaService;
 | 
			
		||||
            this.Logger = logger;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<Connection> CreateConnection(ulong guildId, IVoiceChannel voiceChannel)
 | 
			
		||||
@@ -42,9 +46,9 @@ namespace Kasbot.Services
 | 
			
		||||
            var media = new Media()
 | 
			
		||||
            {
 | 
			
		||||
                Message = Context.Message,
 | 
			
		||||
                Search = arguments,
 | 
			
		||||
                Search = arguments.Trim(),
 | 
			
		||||
                Flags = flags,
 | 
			
		||||
                Name = "",
 | 
			
		||||
                Name = string.Empty,
 | 
			
		||||
            };
 | 
			
		||||
            var guildId = Context.Guild.Id;
 | 
			
		||||
            var userVoiceChannel = (Context.User as IVoiceState).VoiceChannel;
 | 
			
		||||
@@ -69,46 +73,49 @@ namespace Kasbot.Services
 | 
			
		||||
        {
 | 
			
		||||
            var startPlay = conn.Queue.Count == 0;
 | 
			
		||||
 | 
			
		||||
            media.Search.Trim();
 | 
			
		||||
            var mediaType = UrlResolver.GetSearchType(media.Search);
 | 
			
		||||
 | 
			
		||||
            switch (YoutubeService.GetSearchType(media.Search))
 | 
			
		||||
            Logger.Debug($"Enqueueing {media.Search} as {mediaType}");
 | 
			
		||||
 | 
			
		||||
            switch (mediaType)
 | 
			
		||||
            {
 | 
			
		||||
                case SearchType.StringSearch:
 | 
			
		||||
                case SearchType.VideoURL:
 | 
			
		||||
                    media = await YoutubeService.DownloadMetadataFromYoutube(media);
 | 
			
		||||
                case SearchType.SpotifyTrack:
 | 
			
		||||
                    Logger.Debug($"Fetching {media.Search} as {mediaType}");
 | 
			
		||||
 | 
			
		||||
                    if (media.VideoId == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        await media.Channel.SendTemporaryMessageAsync($"No video found for \"{media.Search}\".");
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    media = await MediaService.FetchSingleMedia(media, mediaType);
 | 
			
		||||
 | 
			
		||||
                    conn.Queue.Enqueue(media);
 | 
			
		||||
                    if (startPlay)
 | 
			
		||||
                        await PlayNext(guildId);
 | 
			
		||||
                    else
 | 
			
		||||
                    if (!startPlay && !media.Flags.Silent)
 | 
			
		||||
                    {
 | 
			
		||||
                        var message = $"Queued **{media.Name}** *({media.Length.TotalMinutes:00}:{media.Length.Seconds:00})*";
 | 
			
		||||
                        media.QueueMessage = await media.Channel.SendMessageAsync(message);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
                case SearchType.ChannelURL:
 | 
			
		||||
                case SearchType.PlaylistURL:
 | 
			
		||||
                    var collection = await YoutubeService.DownloadPlaylistMetadataFromYoutube(media.Message, media.Search);
 | 
			
		||||
                    Logger.Debug($"Enqueueing {media.Search} as {mediaType}");
 | 
			
		||||
 | 
			
		||||
                    collection.Medias.ForEach(m => conn.Queue.Enqueue(m));
 | 
			
		||||
                    
 | 
			
		||||
                    await media.Channel.SendMessageAsync($"Queued **{collection.Medias.Count}** items from *{collection.CollectionName}* playlist.");
 | 
			
		||||
 | 
			
		||||
                    if (startPlay)
 | 
			
		||||
                        await PlayNext(guildId);
 | 
			
		||||
                    conn.Queue.Enqueue(media);
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
                case SearchType.None:
 | 
			
		||||
                default:
 | 
			
		||||
                case SearchType.VideoPlaylistURL:
 | 
			
		||||
                case SearchType.YoutubePlaylist:
 | 
			
		||||
                case SearchType.SpotifyPlaylist:
 | 
			
		||||
                case SearchType.SpotifyAlbum:
 | 
			
		||||
                    Logger.Debug($"Fetching {media.Search} as {mediaType}");
 | 
			
		||||
 | 
			
		||||
                    var mediaCollection = await MediaService.FetchMediaCollection(media, mediaType);
 | 
			
		||||
 | 
			
		||||
                    mediaCollection.Medias.ForEach(m => conn.Queue.Enqueue(m));
 | 
			
		||||
 | 
			
		||||
                    Logger.Debug($"Enqueueing {media.Search} as {mediaType}");
 | 
			
		||||
 | 
			
		||||
                    await media.Channel.SendMessageAsync($"Queued **{mediaCollection.Medias.Count}** items from *{mediaCollection.CollectionName}* playlist.");
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (startPlay)
 | 
			
		||||
                await PlayNext(guildId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task PlayNext(ulong guildId)
 | 
			
		||||
@@ -134,16 +141,23 @@ namespace Kasbot.Services
 | 
			
		||||
                await CreateConnection(guildId, voiceChannel);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var mp3Stream = await YoutubeService.DownloadAudioFromYoutube(nextMedia);
 | 
			
		||||
            Logger.Debug($"Downloading {nextMedia.Name}");
 | 
			
		||||
 | 
			
		||||
            var mp3Stream = await MediaService.DownloadAudioFromYoutube(nextMedia);
 | 
			
		||||
 | 
			
		||||
            Logger.Debug($"Playing {nextMedia.Name}");
 | 
			
		||||
 | 
			
		||||
            if (mp3Stream == null)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Error($"Failed to download {nextMedia.Name}");
 | 
			
		||||
                await Stop(guildId);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var audioClient = Clients[guildId].AudioClient;
 | 
			
		||||
 | 
			
		||||
            Logger.Information($"Playing {nextMedia.Name}");
 | 
			
		||||
 | 
			
		||||
            if (!nextMedia.Flags.Silent)
 | 
			
		||||
            {
 | 
			
		||||
                var message = $"⏯ Playing: **{nextMedia.Name}** *({nextMedia.Length.TotalMinutes:00}:{nextMedia.Length.Seconds:00})*";
 | 
			
		||||
@@ -164,6 +178,7 @@ namespace Kasbot.Services
 | 
			
		||||
                {
 | 
			
		||||
                    if (ac.Exception != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        Logger.Error(ac.Exception, $"Error in stream: {ac.Exception.Message}");
 | 
			
		||||
                        await nextMedia.Channel.SendTemporaryMessageAsync("Error in stream: " + ac.Exception.ToString());
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user