added play counter and last.fm auth to CLI
This commit is contained in:
parent
2cd93a61aa
commit
1ea964b341
@ -34,6 +34,8 @@ namespace Selector.CLI
|
||||
/// Spotify app secret
|
||||
/// </summary>
|
||||
public string ClientSecret { get; set; }
|
||||
public string LastfmClient { get; set; }
|
||||
public string LastfmSecret { get; set; }
|
||||
public WatcherOptions WatcherOptions { get; set; } = new();
|
||||
public DatabaseOptions DatabaseOptions { get; set; } = new();
|
||||
public RedisOptions RedisOptions { get; set; } = new();
|
||||
@ -60,6 +62,7 @@ namespace Selector.CLI
|
||||
public string Name { get; set; }
|
||||
public string AccessKey { get; set; }
|
||||
public string RefreshKey { get; set; }
|
||||
public string LastFmUsername { get; set; }
|
||||
public int PollPeriod { get; set; } = 5000;
|
||||
public WatcherType Type { get; set; } = WatcherType.Player;
|
||||
public List<Consumers> Consumers { get; set; } = default;
|
||||
@ -71,7 +74,7 @@ namespace Selector.CLI
|
||||
|
||||
enum Consumers
|
||||
{
|
||||
AudioFeatures, AudioFeaturesCache, CacheWriter, Publisher
|
||||
AudioFeatures, AudioFeaturesCache, CacheWriter, Publisher, PlayCounter
|
||||
}
|
||||
|
||||
class DatabaseOptions {
|
||||
|
@ -9,6 +9,7 @@ using NLog.Extensions.Logging;
|
||||
|
||||
using Selector.Model;
|
||||
using Selector.Cache;
|
||||
using IF.Lastfm.Core.Api;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Selector.CLI
|
||||
@ -45,6 +46,18 @@ namespace Selector.CLI
|
||||
//services.AddSingleton<IRefreshTokenFactoryProvider, RefreshTokenFactoryProvider>();
|
||||
services.AddSingleton<IRefreshTokenFactoryProvider, CachingRefreshTokenFactoryProvider>();
|
||||
|
||||
if(config.LastfmClient is not null)
|
||||
{
|
||||
Console.WriteLine("> Adding Last.fm credentials...");
|
||||
|
||||
var lastAuth = new LastAuth(config.LastfmClient, config.LastfmSecret);
|
||||
services.AddSingleton<LastAuth>(lastAuth);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("> No Last.fm credentials, skipping init...");
|
||||
}
|
||||
|
||||
// DB
|
||||
if (config.DatabaseOptions.Enabled)
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using IF.Lastfm.Core.Api;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@ -25,6 +26,7 @@ namespace Selector.CLI
|
||||
private readonly IWatcherFactory WatcherFactory;
|
||||
private readonly IWatcherCollectionFactory WatcherCollectionFactory;
|
||||
private readonly IRefreshTokenFactoryProvider SpotifyFactory;
|
||||
private readonly LastAuth LastAuth;
|
||||
|
||||
private readonly IDatabaseAsync Cache;
|
||||
private readonly ISubscriber Subscriber;
|
||||
@ -37,6 +39,7 @@ namespace Selector.CLI
|
||||
IRefreshTokenFactoryProvider spotifyFactory,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<RootOptions> config,
|
||||
LastAuth lastAuth = null,
|
||||
IDatabaseAsync cache = null,
|
||||
ISubscriber subscriber = null
|
||||
) {
|
||||
@ -46,6 +49,7 @@ namespace Selector.CLI
|
||||
WatcherFactory = watcherFactory;
|
||||
WatcherCollectionFactory = watcherCollectionFactory;
|
||||
SpotifyFactory = spotifyFactory;
|
||||
LastAuth = lastAuth;
|
||||
Cache = cache;
|
||||
Subscriber = subscriber;
|
||||
|
||||
@ -128,6 +132,22 @@ namespace Selector.CLI
|
||||
var pub = new PublisherFactory(Subscriber, LoggerFactory);
|
||||
consumers.Add(await pub.Get());
|
||||
break;
|
||||
|
||||
case Consumers.PlayCounter:
|
||||
if(!string.IsNullOrWhiteSpace(watcherOption.LastFmUsername))
|
||||
{
|
||||
if(LastAuth is null) throw new ArgumentNullException("No Last Auth Injected");
|
||||
|
||||
var client = new LastfmClient(LastAuth);
|
||||
|
||||
var playCount = new PlayCounterFactory(LoggerFactory, client: client, creds: new(){ Username = watcherOption.LastFmUsername });
|
||||
consumers.Add(await playCount.Get());
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError("No Last.fm usernmae provided, skipping play counter");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,9 @@
|
||||
{
|
||||
"name": "Player Watcher",
|
||||
"type": "player",
|
||||
"lastfmusername": "sarsoo",
|
||||
"pollperiod": 2000,
|
||||
"consumers": [ "audiofeaturescache", "cachewriter", "publisher" ]
|
||||
"consumers": [ "audiofeaturescache", "cachewriter", "publisher", "playcounter" ]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
49
Selector/Consumers/Factory/PlayCounterFactory.cs
Normal file
49
Selector/Consumers/Factory/PlayCounterFactory.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using IF.Lastfm.Core.Api;
|
||||
|
||||
namespace Selector
|
||||
{
|
||||
public interface IPlayCounterFactory
|
||||
{
|
||||
public Task<IConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null);
|
||||
}
|
||||
|
||||
public class PlayCounterFactory: IPlayCounterFactory {
|
||||
|
||||
private readonly ILoggerFactory LoggerFactory;
|
||||
private readonly LastfmClient Client;
|
||||
private readonly LastFmCredentials Creds;
|
||||
|
||||
public PlayCounterFactory(ILoggerFactory loggerFactory, LastfmClient client = null, LastFmCredentials creds = null)
|
||||
{
|
||||
LoggerFactory = loggerFactory;
|
||||
Client = client;
|
||||
Creds = creds;
|
||||
}
|
||||
|
||||
public async Task<IConsumer> Get(LastfmClient fmClient = null, LastFmCredentials creds = null, IPlayerWatcher watcher = null)
|
||||
{
|
||||
var client = fmClient ?? Client;
|
||||
|
||||
if(client is null)
|
||||
{
|
||||
throw new ArgumentNullException("No Last.fm client provided");
|
||||
}
|
||||
|
||||
return new PlayCounter(
|
||||
watcher,
|
||||
client.Track,
|
||||
client.Album,
|
||||
client.Artist,
|
||||
client.User,
|
||||
credentials: creds ?? Creds,
|
||||
LoggerFactory.CreateLogger<PlayCounter>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
190
Selector/Consumers/PlayCounter.cs
Normal file
190
Selector/Consumers/PlayCounter.cs
Normal file
@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
using SpotifyAPI.Web;
|
||||
using IF.Lastfm.Core.Api;
|
||||
|
||||
namespace Selector
|
||||
{
|
||||
public class PlayCounter : IConsumer
|
||||
{
|
||||
protected readonly IPlayerWatcher Watcher;
|
||||
protected readonly ITrackApi TrackClient;
|
||||
protected readonly IAlbumApi AlbumClient;
|
||||
protected readonly IArtistApi ArtistClient;
|
||||
protected readonly IUserApi UserClient;
|
||||
protected readonly LastFmCredentials Credentials;
|
||||
protected readonly ILogger<PlayCounter> Logger;
|
||||
|
||||
protected event EventHandler<PlayCount> NewPlayCount;
|
||||
|
||||
public CancellationToken CancelToken { get; set; }
|
||||
|
||||
public AnalysedTrackTimeline Timeline { get; set; } = new();
|
||||
|
||||
public PlayCounter(
|
||||
IPlayerWatcher watcher,
|
||||
ITrackApi trackClient,
|
||||
IAlbumApi albumClient,
|
||||
IArtistApi artistClient,
|
||||
IUserApi userClient,
|
||||
LastFmCredentials credentials = null,
|
||||
ILogger<PlayCounter> logger = null,
|
||||
CancellationToken token = default
|
||||
)
|
||||
{
|
||||
Watcher = watcher;
|
||||
TrackClient = trackClient;
|
||||
AlbumClient = albumClient;
|
||||
ArtistClient = artistClient;
|
||||
UserClient = userClient;
|
||||
Credentials = credentials;
|
||||
Logger = logger ?? NullLogger<PlayCounter>.Instance;
|
||||
CancelToken = token;
|
||||
}
|
||||
|
||||
public void Callback(object sender, ListeningChangeEventArgs e)
|
||||
{
|
||||
if (e.Current is null) return;
|
||||
|
||||
Task.Run(() => { return AsyncCallback(e); }, CancelToken);
|
||||
}
|
||||
|
||||
public async Task AsyncCallback(ListeningChangeEventArgs e)
|
||||
{
|
||||
if (e.Current.Item is FullTrack track)
|
||||
{
|
||||
Logger.LogTrace("Making Last.fm call");
|
||||
|
||||
var trackInfo = TrackClient.GetInfoAsync(track.Name, track.Artists[0].Name, username: Credentials?.Username);
|
||||
var albumInfo = AlbumClient.GetInfoAsync(track.Album.Name, track.Album.Artists[0].Name, username: Credentials?.Username);
|
||||
var artistInfo = ArtistClient.GetInfoAsync(track.Artists[0].Name);
|
||||
// TODO: Null checking on credentials
|
||||
var userInfo = UserClient.GetInfoAsync(Credentials.Username);
|
||||
|
||||
await Task.WhenAll(new Task[] { trackInfo, albumInfo, artistInfo, userInfo });
|
||||
|
||||
int? trackCount = null, albumCount = null, artistCount = null, userCount = null;
|
||||
|
||||
if (trackInfo.IsCompletedSuccessfully)
|
||||
{
|
||||
if (trackInfo.Result.Success)
|
||||
{
|
||||
trackCount = trackInfo.Result.Content.UserPlayCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug($"Track info error [{e.Username}] [{trackInfo.Result.Status}]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError(trackInfo.Exception, $"Track info task faulted, [{e.Username}] [{e.Current.DisplayString()}]");
|
||||
}
|
||||
|
||||
if (albumInfo.IsCompletedSuccessfully)
|
||||
{
|
||||
if (albumInfo.Result.Success)
|
||||
{
|
||||
albumCount = albumInfo.Result.Content.UserPlayCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug($"Album info error [{e.Username}] [{albumInfo.Result.Status}]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError(albumInfo.Exception, $"Album info task faulted, [{e.Username}] [{e.Current.DisplayString()}]");
|
||||
}
|
||||
|
||||
//TODO: Add artist count
|
||||
|
||||
if (userInfo.IsCompletedSuccessfully)
|
||||
{
|
||||
if (userInfo.Result.Success)
|
||||
{
|
||||
userCount = userInfo.Result.Content.Playcount;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug($"User info error [{e.Username}] [{userInfo.Result.Status}]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError(userInfo.Exception, $"User info task faulted, [{e.Username}] [{e.Current.DisplayString()}]");
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Adding Last.fm data [{Credentials.Username}/{e.Username}] [{track.DisplayString()}], track: {trackCount}, album: {albumCount}, artist: {artistCount}, user: {userCount}");
|
||||
|
||||
OnNewPlayCount(new()
|
||||
{
|
||||
Track = trackCount,
|
||||
Album = albumCount,
|
||||
Artist = artistCount,
|
||||
User = userCount,
|
||||
});
|
||||
}
|
||||
else if (e.Current.Item is FullEpisode episode)
|
||||
{
|
||||
Logger.LogDebug($"Ignoring podcast episdoe [{episode.DisplayString()}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError($"Unknown item pulled from API [{e.Current.Item}]");
|
||||
}
|
||||
}
|
||||
|
||||
public void Subscribe(IWatcher watch = null)
|
||||
{
|
||||
var watcher = watch ?? Watcher ?? throw new ArgumentNullException("No watcher provided");
|
||||
|
||||
if (watcher is IPlayerWatcher watcherCast)
|
||||
{
|
||||
watcherCast.ItemChange += Callback;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Provided watcher is not a PlayerWatcher");
|
||||
}
|
||||
}
|
||||
|
||||
public void Unsubscribe(IWatcher watch = null)
|
||||
{
|
||||
var watcher = watch ?? Watcher ?? throw new ArgumentNullException("No watcher provided");
|
||||
|
||||
if (watcher is IPlayerWatcher watcherCast)
|
||||
{
|
||||
watcherCast.ItemChange -= Callback;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Provided watcher is not a PlayerWatcher");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnNewPlayCount(PlayCount args)
|
||||
{
|
||||
NewPlayCount?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayCount
|
||||
{
|
||||
public int? Track { get; set; }
|
||||
public int? Album { get; set; }
|
||||
public int? Artist { get; set; }
|
||||
public int? User { get; set; }
|
||||
}
|
||||
|
||||
public class LastFmCredentials
|
||||
{
|
||||
public string Username { get; set; }
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ namespace Selector
|
||||
|
||||
public static string DisplayString(this Context context) => $"{context.Type}, {context.Uri}";
|
||||
public static string DisplayString(this Device device) => $"{device.Name} ({device.Id}) {device.VolumePercent}%";
|
||||
public static string DisplayString(this TrackAudioFeatures feature) => $"Acou. {feature.Acousticness}, Dance {feature.Danceability}, Energy {feature.Energy}, Instru. {feature.Instrumentalness}, Key {feature.Key}, Live {feature.Liveness}, Loud {feature.Loudness}dB, Mode {feature.Mode}, Speech {feature.Speechiness}, Tempo {feature.Tempo}BPM, Time Sig. {feature.TimeSignature}, Valence {feature.Valence}";
|
||||
public static string DisplayString(this TrackAudioFeatures feature) => $"Acou. {feature.Acousticness}, Dance {feature.Danceability}, Energy {feature.Energy}, Instru. {feature.Instrumentalness}, Key {feature.Key}, Live {feature.Liveness}, Loud {feature.Loudness} dB, Mode {feature.Mode}, Speech {feature.Speechiness}, Tempo {feature.Tempo} BPM, Time Sig. {feature.TimeSignature}, Valence {feature.Valence}";
|
||||
|
||||
public static string DisplayString(this IEnumerable<SimpleArtist> artists) => string.Join(", ", artists.Select(a => a.DisplayString()));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user