added play counter and last.fm auth to CLI

This commit is contained in:
andy 2021-10-31 08:54:11 +00:00
parent 2cd93a61aa
commit 1ea964b341
8 changed files with 279 additions and 3 deletions

View File

@ -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 {

View File

@ -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)
{

View File

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

View File

@ -8,8 +8,9 @@
{
"name": "Player Watcher",
"type": "player",
"lastfmusername": "sarsoo",
"pollperiod": 2000,
"consumers": [ "audiofeaturescache", "cachewriter", "publisher" ]
"consumers": [ "audiofeaturescache", "cachewriter", "publisher", "playcounter" ]
}
]
},

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

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

View File

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