adding general cards, scrobble density, upgrading nuget packages
This commit is contained in:
parent
97659389af
commit
c8170415b9
@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@ -18,8 +18,8 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
|
||||
<PackageReference Include="NLog" Version="4.7.15" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||
<PackageReference Include="NLog" Version="5.0.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Quartz" Version="3.4.0" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.4.0" />
|
||||
<PackageReference Include="SpotifyAPI.Web" Version="6.2.2" />
|
||||
|
@ -12,20 +12,20 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -8,11 +8,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -12,6 +12,9 @@ using StackExchange.Redis;
|
||||
using Selector.Cache;
|
||||
using Selector.Model;
|
||||
using Selector.Model.Extensions;
|
||||
using Selector.Web.NowPlaying;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Selector.Web.Hubs
|
||||
{
|
||||
@ -20,6 +23,7 @@ namespace Selector.Web.Hubs
|
||||
public Task OnNewPlaying(CurrentlyPlayingDTO context);
|
||||
public Task OnNewAudioFeature(TrackAudioFeatures features);
|
||||
public Task OnNewPlayCount(PlayCount playCount);
|
||||
public Task OnNewCard(Card card);
|
||||
}
|
||||
|
||||
public class NowPlayingHub: Hub<INowPlayingHubClient>
|
||||
@ -30,11 +34,14 @@ namespace Selector.Web.Hubs
|
||||
private readonly ApplicationDbContext Db;
|
||||
private readonly IScrobbleRepository ScrobbleRepository;
|
||||
|
||||
private readonly IOptions<NowPlayingOptions> nowOptions;
|
||||
|
||||
public NowPlayingHub(
|
||||
IDatabaseAsync cache,
|
||||
AudioFeaturePuller featurePuller,
|
||||
ApplicationDbContext db,
|
||||
IScrobbleRepository scrobbleRepository,
|
||||
IOptions<NowPlayingOptions> options,
|
||||
PlayCountPuller playCountPuller = null
|
||||
)
|
||||
{
|
||||
@ -43,6 +50,7 @@ namespace Selector.Web.Hubs
|
||||
PlayCountPuller = playCountPuller;
|
||||
Db = db;
|
||||
ScrobbleRepository = scrobbleRepository;
|
||||
nowOptions = options;
|
||||
}
|
||||
|
||||
public async Task OnConnected()
|
||||
@ -108,5 +116,51 @@ namespace Selector.Web.Hubs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendFacts(string track, string artist, string album, string albumArtist)
|
||||
{
|
||||
var user = Db.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == Context.UserIdentifier)
|
||||
.SingleOrDefault()
|
||||
?? throw new SqlNullValueException("No user returned");
|
||||
|
||||
if (user.ScrobbleSavingEnabled())
|
||||
{
|
||||
var artistScrobbles = ScrobbleRepository.GetAll(userId: user.Id, artistName: artist, from: GetMaximumWindow()).ToArray();
|
||||
var artistDensity = artistScrobbles.Density(DateTime.UtcNow - nowOptions.Value.ArtistDensityWindow, DateTime.UtcNow);
|
||||
|
||||
if (artistDensity > nowOptions.Value.ArtistDensityThreshold)
|
||||
{
|
||||
await Clients.Caller.OnNewCard(new()
|
||||
{
|
||||
Content = $"You're on a {artist} binge! {artistDensity} plays/day recently"
|
||||
});
|
||||
}
|
||||
|
||||
var albumDensity = artistScrobbles.Where(s => s.AlbumName.Equals(album, StringComparison.InvariantCultureIgnoreCase)).Density(DateTime.UtcNow - nowOptions.Value.AlbumDensityWindow, DateTime.UtcNow);
|
||||
|
||||
if (albumDensity > nowOptions.Value.AlbumDensityThreshold)
|
||||
{
|
||||
await Clients.Caller.OnNewCard(new()
|
||||
{
|
||||
Content = $"You're on a {album} binge! {albumDensity} plays/day recently"
|
||||
});
|
||||
}
|
||||
|
||||
var trackDensity = artistScrobbles.Where(s => s.TrackName.Equals(track, StringComparison.InvariantCultureIgnoreCase)).Density(DateTime.UtcNow - nowOptions.Value.TrackDensityWindow, DateTime.UtcNow);
|
||||
|
||||
if (albumDensity > nowOptions.Value.TrackDensityThreshold)
|
||||
{
|
||||
await Clients.Caller.OnNewCard(new()
|
||||
{
|
||||
Content = $"You're on a {track} binge! {trackDensity} plays/day recently"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime GetMaximumWindow() => GetMaximumWindow(new TimeSpan[] { nowOptions.Value.ArtistDensityWindow, nowOptions.Value.AlbumDensityWindow, nowOptions.Value.TrackDensityWindow });
|
||||
private DateTime GetMaximumWindow(IEnumerable<TimeSpan> windows) => windows.Select(w => DateTime.UtcNow - w).Min();
|
||||
}
|
||||
}
|
9
Selector.Web/NowPlaying/Card.cs
Normal file
9
Selector.Web/NowPlaying/Card.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
namespace Selector.Web.NowPlaying
|
||||
{
|
||||
public class Card
|
||||
{
|
||||
public string Content { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Selector.Web
|
||||
@ -8,6 +9,7 @@ namespace Selector.Web
|
||||
{
|
||||
config.GetSection(RootOptions.Key).Bind(options);
|
||||
config.GetSection(FormatKeys(new[] { RootOptions.Key, RedisOptions.Key })).Bind(options.RedisOptions);
|
||||
config.GetSection(FormatKeys(new[] { RootOptions.Key, NowPlayingOptions.Key })).Bind(options.NowOptions);
|
||||
}
|
||||
|
||||
public static RootOptions ConfigureOptions(IConfiguration config)
|
||||
@ -40,6 +42,7 @@ namespace Selector.Web
|
||||
public string LastfmSecret { get; set; }
|
||||
|
||||
public RedisOptions RedisOptions { get; set; } = new();
|
||||
public NowPlayingOptions NowOptions { get; set; } = new();
|
||||
|
||||
}
|
||||
|
||||
@ -50,4 +53,18 @@ namespace Selector.Web
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string ConnectionString { get; set; }
|
||||
}
|
||||
|
||||
public class NowPlayingOptions
|
||||
{
|
||||
public const string Key = "Now";
|
||||
|
||||
public TimeSpan ArtistDensityWindow { get; set; } = TimeSpan.FromDays(10);
|
||||
public decimal ArtistDensityThreshold { get; set; } = 5;
|
||||
|
||||
public TimeSpan AlbumDensityWindow { get; set; } = TimeSpan.FromDays(10);
|
||||
public decimal AlbumDensityThreshold { get; set; } = 5;
|
||||
|
||||
public TimeSpan TrackDensityWindow { get; set; } = TimeSpan.FromDays(10);
|
||||
public decimal TrackDensityThreshold { get; set; } = 5;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<audio-feature-card :feature="trackFeatures" v-if="trackFeatures !== null && trackFeatures !== undefined" /></audio-feature-card>
|
||||
<audio-feature-chart-card :feature="trackFeatures" v-if="trackFeatures !== null && trackFeatures !== undefined" /></audio-feature-chart-card>
|
||||
<play-count-card :count="playCount" :track="lastfmTrack" :username="playCount.username" v-if="playCount !== null && playCount !== undefined" /></play-count-card>
|
||||
<info-card v-for="card in cards" :html="card.html"></info-card>
|
||||
<info-card v-for="card in cards" :html="card.content"></info-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
@ -17,7 +17,6 @@
|
||||
},
|
||||
"Selector.Web": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": "true",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
@ -25,4 +24,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,13 +14,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.5" />
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -28,16 +28,20 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="6.0.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.3" />
|
||||
<PackageReference Include="NLog" Version="4.7.15" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.5" />
|
||||
<PackageReference Include="NLog" Version="5.0.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
<Folder Include="NowPlaying\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="NowPlaying\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.Development.json" Condition="Exists('appsettings.Development.json')">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
12
Selector.Web/package-lock.json
generated
12
Selector.Web/package-lock.json
generated
@ -1011,9 +1011,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
|
||||
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
|
||||
"integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
|
||||
"dependencies": {
|
||||
"original": "^1.0.0"
|
||||
},
|
||||
@ -3613,9 +3613,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"eventsource": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
|
||||
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
|
||||
"integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
|
||||
"requires": {
|
||||
"original": "^1.0.0"
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ connection.start()
|
||||
.catch(err => console.error(err));
|
||||
|
||||
interface InfoCard {
|
||||
html: string
|
||||
Content: string
|
||||
}
|
||||
|
||||
interface NowPlaying {
|
||||
@ -64,6 +64,12 @@ const app = Vue.createApp({
|
||||
context.track.album.name,
|
||||
context.track.album.artists[0].name
|
||||
);
|
||||
connection.invoke("SendFacts",
|
||||
context.track.name,
|
||||
context.track.artists[0].name,
|
||||
context.track.album.name,
|
||||
context.track.album.artists[0].name
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -78,6 +84,12 @@ const app = Vue.createApp({
|
||||
console.log(count);
|
||||
this.playCount = count;
|
||||
});
|
||||
|
||||
connection.on("OnNewCard", (card: InfoCard) => {
|
||||
|
||||
console.log(card);
|
||||
this.cards.push(card);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
29
Selector/Scrobble/PlayDensity.cs
Normal file
29
Selector/Scrobble/PlayDensity.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Selector
|
||||
{
|
||||
public static class PlayDensity
|
||||
{
|
||||
public static decimal Density(this IEnumerable<Scrobble> scrobbles, DateTime from, DateTime to)
|
||||
{
|
||||
var filteredScrobbles = scrobbles.Where(s => s.Timestamp > from && s.Timestamp < to);
|
||||
|
||||
var dayDelta = (decimal) (to - from).Days;
|
||||
|
||||
return filteredScrobbles.Count() / dayDelta;
|
||||
}
|
||||
|
||||
public static decimal Density(this IEnumerable<Scrobble> scrobbles)
|
||||
{
|
||||
var minDate = scrobbles.Select(s => s.Timestamp).Min();
|
||||
var maxDate = scrobbles.Select(s => s.Timestamp).Max();
|
||||
|
||||
var dayDelta = (decimal) (maxDate - minDate).Days;
|
||||
|
||||
return scrobbles.Count() / dayDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user