add
This commit is contained in:
195
TomatenMusicCore/Services/SpotifyService.cs
Normal file
195
TomatenMusicCore/Services/SpotifyService.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using SpotifyAPI.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DSharpPlus;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TomatenMusic.Music.Entitites;
|
||||
using TomatenMusic.Services;
|
||||
using TomatenMusic.Music;
|
||||
using Lavalink4NET;
|
||||
using Lavalink4NET.Player;
|
||||
using System.Runtime.Caching;
|
||||
using TomatenMusicCore.Music;
|
||||
|
||||
namespace TomatenMusic.Services
|
||||
{
|
||||
|
||||
public interface ISpotifyService
|
||||
{
|
||||
public Task<MusicActionResponse> ConvertURL(string url);
|
||||
public Task<SpotifyPlaylist> PopulateSpotifyPlaylistAsync(SpotifyPlaylist playlist, FullPlaylist spotifyPlaylist = null);
|
||||
public Task<SpotifyPlaylist> PopulateSpotifyAlbumAsync(SpotifyPlaylist playlist, FullAlbum spotifyAlbum = null);
|
||||
public Task<LavalinkTrack> PopulateTrackAsync(LavalinkTrack track, FullTrack spotifyFullTrack = null);
|
||||
|
||||
}
|
||||
|
||||
public class SpotifyService : SpotifyClient, ISpotifyService
|
||||
{
|
||||
public ILogger _logger { get; set; }
|
||||
public IAudioService _audioService { get; set; }
|
||||
|
||||
public ObjectCache Cache { get; set; }
|
||||
|
||||
public SpotifyService(SpotifyClientConfig config, ILogger<SpotifyService> logger, IAudioService audioService) : base(config)
|
||||
{
|
||||
_logger = logger;
|
||||
_audioService = audioService;
|
||||
Cache = MemoryCache.Default;
|
||||
}
|
||||
|
||||
public async Task<MusicActionResponse> ConvertURL(string url)
|
||||
{
|
||||
string trackId = url
|
||||
.Replace("https://open.spotify.com/track/", "")
|
||||
.Replace("https://open.spotify.com/album/", "")
|
||||
.Replace("https://open.spotify.com/playlist/", "")
|
||||
.Substring(0, 22);
|
||||
|
||||
_logger.LogDebug($"Starting spotify conversion for: {url}");
|
||||
|
||||
if (url.StartsWith("https://open.spotify.com/track"))
|
||||
{
|
||||
FullTrack sTrack = Cache.Contains(trackId) ? Cache.Get(trackId) as FullTrack : await Tracks.Get(trackId);
|
||||
|
||||
_logger.LogInformation($"Searching youtube from spotify with query: {sTrack.Name} {String.Join(" ", sTrack.Artists)}");
|
||||
|
||||
var track = new TomatenMusicTrack(
|
||||
await _audioService.GetTrackAsync($"{sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}"
|
||||
, Lavalink4NET.Rest.SearchMode.YouTube));
|
||||
|
||||
if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
|
||||
|
||||
Cache.Add(trackId, sTrack, DateTimeOffset.MaxValue);
|
||||
|
||||
return new MusicActionResponse(await FullTrackContext.PopulateAsync(track, sTrack));
|
||||
|
||||
}
|
||||
else if (url.StartsWith("https://open.spotify.com/album"))
|
||||
{
|
||||
TrackList tracks = new TrackList();
|
||||
|
||||
FullAlbum album = Cache.Contains(trackId) ? Cache.Get(trackId) as FullAlbum : await Albums.Get(trackId);
|
||||
|
||||
foreach (var sTrack in await PaginateAll(album.Tracks))
|
||||
{
|
||||
_logger.LogInformation($"Searching youtube from spotify with query: {sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}");
|
||||
var track = new TomatenMusicTrack(
|
||||
await _audioService.GetTrackAsync($"{sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}"
|
||||
, Lavalink4NET.Rest.SearchMode.YouTube));
|
||||
|
||||
if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
|
||||
|
||||
tracks.Add(await FullTrackContext.PopulateAsync(track, spotifyId: sTrack.Uri.Replace("spotify:track:", "")));
|
||||
}
|
||||
Uri uri;
|
||||
Uri.TryCreate(url, UriKind.Absolute, out uri);
|
||||
|
||||
SpotifyPlaylist playlist = new SpotifyPlaylist(album.Name, album.Id, tracks, uri);
|
||||
await PopulateSpotifyAlbumAsync(playlist, album);
|
||||
|
||||
Cache.Add(trackId, album, DateTimeOffset.MaxValue);
|
||||
|
||||
return new MusicActionResponse(playlist: playlist);
|
||||
|
||||
}
|
||||
else if (url.StartsWith("https://open.spotify.com/playlist"))
|
||||
{
|
||||
|
||||
TrackList tracks = new TrackList();
|
||||
|
||||
FullPlaylist spotifyPlaylist = Cache.Contains(trackId) ? Cache.Get(trackId) as FullPlaylist : await Playlists.Get(trackId);
|
||||
|
||||
foreach (var sTrack in await PaginateAll(spotifyPlaylist.Tracks))
|
||||
{
|
||||
if (sTrack.Track is FullTrack)
|
||||
{
|
||||
FullTrack fullTrack = (FullTrack)sTrack.Track;
|
||||
_logger.LogInformation($"Searching youtube from spotify with query: {fullTrack.Name} {String.Join(" ", fullTrack.Artists.ConvertAll(artist => artist.Name))}");
|
||||
|
||||
var track = new TomatenMusicTrack(
|
||||
await _audioService.GetTrackAsync($"{fullTrack.Name} {String.Join(" ", fullTrack.Artists.ConvertAll(artist => artist.Name))}"
|
||||
, Lavalink4NET.Rest.SearchMode.YouTube));
|
||||
|
||||
if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
|
||||
|
||||
tracks.Add(await FullTrackContext.PopulateAsync(track, fullTrack));
|
||||
}
|
||||
|
||||
}
|
||||
Uri uri;
|
||||
Uri.TryCreate(url, UriKind.Absolute, out uri);
|
||||
SpotifyPlaylist playlist = new SpotifyPlaylist(spotifyPlaylist.Name, spotifyPlaylist.Id, tracks, uri);
|
||||
await PopulateSpotifyPlaylistAsync(playlist, spotifyPlaylist);
|
||||
|
||||
Cache.Add(trackId, spotifyPlaylist, DateTimeOffset.MaxValue);
|
||||
|
||||
return new MusicActionResponse(playlist: playlist);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<SpotifyPlaylist> PopulateSpotifyPlaylistAsync(SpotifyPlaylist playlist, FullPlaylist spotifyPlaylist = null)
|
||||
{
|
||||
FullPlaylist list = spotifyPlaylist;
|
||||
if (list == null)
|
||||
list = await this.Playlists.Get(playlist.Identifier);
|
||||
|
||||
string desc = list.Description;
|
||||
|
||||
playlist.Description = desc.Substring(0, Math.Min(desc.Length, 1024)) + (desc.Length > 1020 ? "..." : " ");
|
||||
if (playlist.Description.Length < 2)
|
||||
playlist.Description = "None";
|
||||
|
||||
playlist.AuthorUri = new Uri($"https://open.spotify.com/user/{list.Owner.Id}");
|
||||
playlist.AuthorName = list.Owner.DisplayName;
|
||||
playlist.Followers = list.Followers.Total;
|
||||
playlist.Url = new Uri($"https://open.spotify.com/playlist/{playlist.Identifier}");
|
||||
try
|
||||
{
|
||||
playlist.AuthorThumbnail = new Uri(list.Owner.Images.First().Url);
|
||||
}
|
||||
catch (Exception ex) { }
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
public async Task<SpotifyPlaylist> PopulateSpotifyAlbumAsync(SpotifyPlaylist playlist, FullAlbum spotifyAlbum = null)
|
||||
{
|
||||
FullAlbum list = spotifyAlbum;
|
||||
if (list == null)
|
||||
list = await this.Albums.Get(playlist.Identifier);
|
||||
|
||||
string desc = list.Label;
|
||||
|
||||
playlist.Description = desc.Substring(0, Math.Min(desc.Length, 1024)) + (desc.Length > 1020 ? "..." : " ");
|
||||
playlist.AuthorUri = new Uri($"https://open.spotify.com/user/{list.Artists.First().Uri}");
|
||||
playlist.AuthorName = list.Artists.First().Name;
|
||||
playlist.Followers = list.Popularity;
|
||||
playlist.Url = new Uri($"https://open.spotify.com/album/{playlist.Identifier}");
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
public async Task<LavalinkTrack> PopulateTrackAsync(LavalinkTrack track, FullTrack spotifyFullTrack)
|
||||
{
|
||||
FullTrackContext context = (FullTrackContext)track.Context;
|
||||
if (context.SpotifyIdentifier == null)
|
||||
return track;
|
||||
|
||||
FullTrack spotifyTrack = spotifyFullTrack;
|
||||
if (spotifyTrack == null)
|
||||
spotifyTrack = await Tracks.Get(context.SpotifyIdentifier);
|
||||
|
||||
context.SpotifyAlbum = spotifyTrack.Album;
|
||||
context.SpotifyArtists = spotifyTrack.Artists;
|
||||
context.SpotifyPopularity = spotifyTrack.Popularity;
|
||||
context.SpotifyUri = new Uri($"https://open.spotify.com/track/{context.SpotifyIdentifier}");
|
||||
track.Context = context;
|
||||
|
||||
return track;
|
||||
}
|
||||
}
|
||||
}
|
108
TomatenMusicCore/Services/TrackProvider.cs
Normal file
108
TomatenMusicCore/Services/TrackProvider.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Lavalink4NET;
|
||||
using Lavalink4NET.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using TomatenMusic.Music.Entitites;
|
||||
using TomatenMusic.Services;
|
||||
using TomatenMusicCore.Music;
|
||||
|
||||
namespace TomatenMusic.Music
|
||||
{
|
||||
public class TrackProvider
|
||||
{
|
||||
public ISpotifyService _spotifyService { get; set; }
|
||||
public IAudioService _audioService { get; set; }
|
||||
public YoutubeService _youtubeService { get; set; }
|
||||
|
||||
public TrackProvider(ISpotifyService spotify, IAudioService audioService, YoutubeService youtubeService)
|
||||
{
|
||||
_audioService = audioService;
|
||||
_spotifyService = spotify;
|
||||
_youtubeService = youtubeService;
|
||||
}
|
||||
|
||||
public async Task<MusicActionResponse> SearchAsync(string query, bool withSearchResults = false)
|
||||
{
|
||||
|
||||
Uri uri;
|
||||
TrackLoadResponsePayload loadResult;
|
||||
bool isSearch = true;
|
||||
|
||||
if (query.StartsWith("https://open.spotify.com"))
|
||||
{
|
||||
return await _spotifyService.ConvertURL(query);
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(query, UriKind.Absolute, out uri))
|
||||
{
|
||||
loadResult = await _audioService.LoadTracksAsync(uri.ToString());
|
||||
isSearch = false;
|
||||
}
|
||||
else
|
||||
loadResult = await _audioService.LoadTracksAsync(query, SearchMode.YouTube);
|
||||
|
||||
if (uri != null && uri.AbsolutePath.Contains("."))
|
||||
return await SearchAsync(uri);
|
||||
|
||||
if (loadResult.LoadType == TrackLoadType.LoadFailed) throw new ArgumentException("Track loading failed");
|
||||
|
||||
if (loadResult.LoadType == TrackLoadType.NoMatches) throw new FileNotFoundException("Query resulted in no Matches");
|
||||
|
||||
if (withSearchResults && loadResult.LoadType == TrackLoadType.SearchResult)
|
||||
{
|
||||
return new MusicActionResponse(tracks: await FullTrackContext.PopulateTracksAsync(new TrackList(loadResult.Tracks)));
|
||||
}
|
||||
|
||||
if (loadResult.LoadType == TrackLoadType.PlaylistLoaded && !isSearch)
|
||||
return new MusicActionResponse(
|
||||
playlist: await _youtubeService.PopulatePlaylistAsync(
|
||||
new YoutubePlaylist(loadResult.PlaylistInfo.Name, await FullTrackContext.PopulateTracksAsync(new TrackList(loadResult.Tracks)), ParseListId(query))));
|
||||
else
|
||||
return new MusicActionResponse(await FullTrackContext.PopulateAsync(new TomatenMusicTrack(loadResult.Tracks.First())));
|
||||
|
||||
}
|
||||
|
||||
public async Task<MusicActionResponse> SearchAsync(Uri fileUri)
|
||||
{
|
||||
|
||||
var loadResult = new TomatenMusicTrack(await _audioService.GetTrackAsync(fileUri.ToString()));
|
||||
loadResult.Context = new FullTrackContext
|
||||
{
|
||||
IsFile = true
|
||||
};
|
||||
|
||||
if (loadResult == null)
|
||||
throw new FileNotFoundException("The file was not found");
|
||||
|
||||
return new MusicActionResponse(loadResult);
|
||||
|
||||
}
|
||||
|
||||
public string ParseListId(string url)
|
||||
{
|
||||
var uri = new Uri(url, UriKind.Absolute);
|
||||
|
||||
// you can check host here => uri.Host <= "www.youtube.com"
|
||||
|
||||
var query = HttpUtility.ParseQueryString(uri.Query);
|
||||
|
||||
var videoId = string.Empty;
|
||||
|
||||
if (query.AllKeys.Contains("list"))
|
||||
{
|
||||
videoId = query["list"];
|
||||
}
|
||||
else
|
||||
{
|
||||
videoId = uri.Segments.Last();
|
||||
}
|
||||
|
||||
return videoId;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
149
TomatenMusicCore/Services/YoutubeService.cs
Normal file
149
TomatenMusicCore/Services/YoutubeService.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using Google.Apis.Services;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TomatenMusic.Music.Entitites;
|
||||
using System.Linq;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static TomatenMusic.TomatenMusicBot;
|
||||
using Lavalink4NET.Player;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Lavalink4NET;
|
||||
using TomatenMusicCore.Music;
|
||||
|
||||
namespace TomatenMusic.Services
|
||||
{
|
||||
public class YoutubeService
|
||||
{
|
||||
public YouTubeService Service { get; }
|
||||
public ILogger<YoutubeService> _logger { get; set; }
|
||||
public YoutubeService(ILogger<YoutubeService> logger, ConfigJson config)
|
||||
{
|
||||
Service = new YouTubeService(new BaseClientService.Initializer()
|
||||
{
|
||||
ApiKey = config.YoutubeAPIKey,
|
||||
ApplicationName = "TomatenMusic"
|
||||
});
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<LavalinkTrack> PopulateTrackInfoAsync(LavalinkTrack track)
|
||||
{
|
||||
var video = await GetVideoAsync(track.TrackIdentifier);
|
||||
var channel = await GetChannelAsync(video.Snippet.ChannelId);
|
||||
FullTrackContext context = track.Context == null ? new FullTrackContext() : (FullTrackContext)track.Context;
|
||||
|
||||
if (channel.Statistics.SubscriberCount != null)
|
||||
context.YoutubeAuthorSubs = (ulong) channel.Statistics.SubscriberCount;
|
||||
context.YoutubeAuthorThumbnail = new Uri(channel.Snippet.Thumbnails.Default__.Url);
|
||||
context.YoutubeAuthorUri = new Uri($"https://www.youtube.com/channel/{channel.Id}");
|
||||
string desc = video.Snippet.Description;
|
||||
|
||||
context.YoutubeDescription = desc.Substring(0, Math.Min(desc.Length, 1024)) + (desc.Length > 1020 ? "..." : " ");
|
||||
if (video.Statistics.LikeCount != null)
|
||||
context.YoutubeLikes = (ulong) video.Statistics.LikeCount;
|
||||
context.YoutubeTags = video.Snippet.Tags;
|
||||
|
||||
try
|
||||
{
|
||||
context.YoutubeThumbnail = new Uri(video.Snippet.Thumbnails.High.Url);
|
||||
}catch (Exception ex) { }
|
||||
|
||||
context.YoutubeUploadDate = (DateTime)video.Snippet.PublishedAt;
|
||||
context.YoutubeViews = (ulong)video.Statistics.ViewCount;
|
||||
context.YoutubeCommentCount = video.Statistics.CommentCount;
|
||||
track.Context = context;
|
||||
return track;
|
||||
}
|
||||
|
||||
public async Task<List<LavalinkTrack>> PopulateTrackListAsync(IEnumerable<LavalinkTrack> tracks)
|
||||
{
|
||||
List<LavalinkTrack> newTracks = new List<LavalinkTrack>();
|
||||
foreach (var track in tracks)
|
||||
newTracks.Add(await PopulateTrackInfoAsync(track));
|
||||
|
||||
return newTracks;
|
||||
}
|
||||
public async Task<ILavalinkPlaylist> PopulatePlaylistAsync(YoutubePlaylist playlist)
|
||||
{
|
||||
var list = await GetPlaylistAsync(playlist.Identifier);
|
||||
var channel = await GetChannelAsync(list.Snippet.ChannelId);
|
||||
|
||||
string desc = list.Snippet.Description;
|
||||
|
||||
playlist.Description = desc.Substring(0, Math.Min(desc.Length, 1024)) + (desc.Length > 1020 ? "..." : " ");
|
||||
if (playlist.Description.Length < 2)
|
||||
playlist.Description = "None";
|
||||
|
||||
try
|
||||
{
|
||||
playlist.Thumbnail = new Uri(list.Snippet.Thumbnails.Maxres.Url);
|
||||
}catch (Exception ex) { }
|
||||
playlist.AuthorName = channel.Snippet.Title;
|
||||
playlist.CreationTime = (DateTime)list.Snippet.PublishedAt;
|
||||
playlist.YoutubeItem = list;
|
||||
playlist.AuthorThumbnail = new Uri(channel.Snippet.Thumbnails.Default__.Url);
|
||||
playlist.AuthorUri = new Uri($"https://www.youtube.com/channels/{channel.Id}");
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
public async Task<Video> GetVideoAsync(string id)
|
||||
{
|
||||
var search = Service.Videos.List("contentDetails,id,liveStreamingDetails,localizations,player,recordingDetails,snippet,statistics,status,topicDetails");
|
||||
search.Id = id;
|
||||
var response = await search.ExecuteAsync();
|
||||
return response.Items.First();
|
||||
}
|
||||
|
||||
public async Task<Channel> GetChannelAsync(string id)
|
||||
{
|
||||
var search = Service.Channels.List("brandingSettings,contentDetails,contentOwnerDetails,id,localizations,snippet,statistics,status,topicDetails");
|
||||
search.Id = id;
|
||||
var response = await search.ExecuteAsync();
|
||||
|
||||
return response.Items.First();
|
||||
}
|
||||
public async Task<Playlist> GetPlaylistAsync(string id)
|
||||
{
|
||||
var search = Service.Playlists.List("snippet,contentDetails,status");
|
||||
search.Id = id;
|
||||
var response = await search.ExecuteAsync();
|
||||
|
||||
return response.Items.First();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchResult>> GetRelatedVideosAsync(string id)
|
||||
{
|
||||
var search = Service.Search.List("snippet");
|
||||
search.RelatedToVideoId = id;
|
||||
search.Type = "video";
|
||||
var response = await search.ExecuteAsync();
|
||||
return response.Items.Where(x => x.Snippet != null);
|
||||
}
|
||||
|
||||
public async Task<LavalinkTrack> GetRelatedTrackAsync(string id, List<string> excludeIds)
|
||||
{
|
||||
var audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
|
||||
|
||||
var videos = await GetRelatedVideosAsync(id);
|
||||
SearchResult video = null;
|
||||
foreach (var vid in videos)
|
||||
video = videos.First(x => !excludeIds.Contains(x.Id.VideoId));
|
||||
|
||||
if (video == null)
|
||||
video = videos.FirstOrDefault();
|
||||
|
||||
var loadResult = new TomatenMusicTrack(await audioService.GetTrackAsync($"https://youtu.be/{video.Id.VideoId}"));
|
||||
|
||||
if (loadResult == null)
|
||||
throw new Exception("An Error occurred while processing the Request");
|
||||
|
||||
return await FullTrackContext.PopulateAsync(loadResult);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user