adding spotify integration

This commit is contained in:
2023-12-22 21:08:20 -03:00
parent 4b719e42e3
commit c536678818
9 changed files with 447 additions and 108 deletions

View 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>();
}
}

View 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;
}
}
}

View 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
}
}

View File

@@ -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>();
}
}