From 220fa33bf27d701242a459ed827e814f1b5a9ce7 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Tue, 23 Apr 2024 21:48:51 +0700
Subject: [PATCH 01/18] fix: playlist view early loading crash
---
MusicX/ViewModels/PlaylistViewModel.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MusicX/ViewModels/PlaylistViewModel.cs b/MusicX/ViewModels/PlaylistViewModel.cs
index 5ceb4999..1bc0a8c3 100644
--- a/MusicX/ViewModels/PlaylistViewModel.cs
+++ b/MusicX/ViewModels/PlaylistViewModel.cs
@@ -56,7 +56,7 @@ public async ValueTask LoadMore()
{
try
{
- if (Tracks.Count >= Playlist.Count)
+ if (Tracks.Count == 0 || Tracks.Count >= Playlist.Count)
return;
VisibleLoadingMore = Visibility.Visible;
var response = await vkService.AudioGetAsync(Playlist.Id, Playlist.OwnerId, Playlist.AccessKey, Tracks.Count, 40);
From fa1219b2088021ab593e57e69dd110c3535fcac8 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Tue, 23 Apr 2024 22:00:53 +0700
Subject: [PATCH 02/18] fix: artist blacklist style missing
---
MusicX/Views/SettingsView.xaml | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/MusicX/Views/SettingsView.xaml b/MusicX/Views/SettingsView.xaml
index 9c780b5f..29115330 100644
--- a/MusicX/Views/SettingsView.xaml
+++ b/MusicX/Views/SettingsView.xaml
@@ -153,14 +153,15 @@
-
-
+
-
+
+ Tag="{Binding}">
+
+
+
+
-
-
+
+
Date: Wed, 24 Apr 2024 19:36:53 +0700
Subject: [PATCH 03/18] fix: enable mousewheel scroll for queue
---
MusicX/Controls/PlayerControl.xaml | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/MusicX/Controls/PlayerControl.xaml b/MusicX/Controls/PlayerControl.xaml
index 6a4e9d50..1cf33e81 100644
--- a/MusicX/Controls/PlayerControl.xaml
+++ b/MusicX/Controls/PlayerControl.xaml
@@ -629,6 +629,19 @@
+
+
+
+
+
+
+
Date: Wed, 24 Apr 2024 21:09:42 +0700
Subject: [PATCH 04/18] restyle titleblock button
---
MusicX/App.xaml | 1 +
MusicX/Controls/Blocks/TitleBlockControl.xaml | 75 ++++++++----
.../Controls/Blocks/TitleBlockControl.xaml.cs | 48 +++-----
MusicX/Styles/HyperlinkButtonStyles.xaml | 109 ++++++++++++++++++
4 files changed, 174 insertions(+), 59 deletions(-)
create mode 100644 MusicX/Styles/HyperlinkButtonStyles.xaml
diff --git a/MusicX/App.xaml b/MusicX/App.xaml
index 502aef82..1b91eb80 100644
--- a/MusicX/App.xaml
+++ b/MusicX/App.xaml
@@ -18,6 +18,7 @@
+
pack://application:,,,/;component/Fonts/#Segoe Fluent Icons
diff --git a/MusicX/Controls/Blocks/TitleBlockControl.xaml b/MusicX/Controls/Blocks/TitleBlockControl.xaml
index bf5e812c..288b731d 100644
--- a/MusicX/Controls/Blocks/TitleBlockControl.xaml
+++ b/MusicX/Controls/Blocks/TitleBlockControl.xaml
@@ -5,9 +5,35 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfui="clr-namespace:Wpf.Ui.Controls;assembly=Wpf.Ui"
+ xmlns:system="clr-namespace:System;assembly=System.Runtime"
+ xmlns:models="clr-namespace:MusicX.Core.Models;assembly=MusicX.Core"
d:DesignHeight="450"
d:DesignWidth="800"
- mc:Ignorable="d">
+ mc:Ignorable="d"
+ d:DataContext="{d:DesignInstance models:Block}">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
+
-
-
-
diff --git a/MusicX/Controls/Blocks/TitleBlockControl.xaml.cs b/MusicX/Controls/Blocks/TitleBlockControl.xaml.cs
index c6d39594..83f276c1 100644
--- a/MusicX/Controls/Blocks/TitleBlockControl.xaml.cs
+++ b/MusicX/Controls/Blocks/TitleBlockControl.xaml.cs
@@ -44,12 +44,14 @@ private void TitleBlockControl_Loaded(object sender, DependencyPropertyChangedEv
Title.FontSize = 15;
}
- Title.Text = block.Layout.Title;
+ Title.Content = block.Layout.Title;
if(block.Layout.TopTitle is not null || block.Layout.Subtitle is not null)
{
Subtitle.Text = block.Layout.TopTitle?.Text ?? block.Layout.Subtitle;
Subtitle.Visibility = Visibility.Visible;
+ if (block.Actions.Count == 1)
+ Subtitle.Margin = new(11, 0, 11, 0);
}
if (block.Badge != null)
@@ -58,14 +60,13 @@ private void TitleBlockControl_Loaded(object sender, DependencyPropertyChangedEv
BadgeHeader.Visibility = Visibility.Visible;
}
- if (block.Buttons != null && block.Buttons.Count > 0) //ios
+ if (block.Buttons is { Count: > 0 }) //ios
{
if (block.Buttons[0].Options.Count > 0)
{
ButtonsGrid.Visibility = Visibility.Visible;
TitleButtons.Text = block.Buttons[0].Title;
Buttons.Visibility = Visibility.Visible;
- MoreButton.Visibility = Visibility.Collapsed;
foreach (var option in block.Buttons[0].Options)
{
Buttons.Items.Add(new TextBlock() { Text = option.Text });
@@ -73,47 +74,24 @@ private void TitleBlockControl_Loaded(object sender, DependencyPropertyChangedEv
//Buttons.SelectedIndex = 0;
return;
}
- else
- {
- MoreButton.Visibility = Visibility.Visible;
-
- MoreButton.Content = block.Buttons[0].Title;
-
- return;
-
- }
}
else
{
- if(block.Actions.Count > 0)
+ if(block.Actions.Count > 0 && block.Actions[0].Options.Count > 0)
{
-
- if (block.Actions[0].Options.Count > 0) //android
- {
- ButtonsGrid.Visibility = Visibility.Visible;
- TitleButtons.Text = block.Actions[0].Title;
- Buttons.Visibility = Visibility.Visible;
- MoreButton.Visibility = Visibility.Collapsed;
+ //android
+ ButtonsGrid.Visibility = Visibility.Visible;
+ TitleButtons.Text = block.Actions[0].Title;
+ Buttons.Visibility = Visibility.Visible;
- foreach (var option in block.Actions[0].Options)
- {
- Buttons.Items.Add(new TextBlock() { Text = option.Text });
- }
-
- return;
- }
- else
+ foreach (var option in block.Actions[0].Options)
{
- MoreButton.Visibility = Visibility.Visible;
-
- MoreButton.Content = block.Actions[0].Title;
-
- return;
-
+ Buttons.Items.Add(new TextBlock() { Text = option.Text });
}
-
+
+ return;
}
return;
diff --git a/MusicX/Styles/HyperlinkButtonStyles.xaml b/MusicX/Styles/HyperlinkButtonStyles.xaml
new file mode 100644
index 00000000..c6833f10
--- /dev/null
+++ b/MusicX/Styles/HyperlinkButtonStyles.xaml
@@ -0,0 +1,109 @@
+
+ 0,5,0,0
+
+
+
\ No newline at end of file
From 04f0f42f2e78e1e05b1f8ae08d0e5d9ca1f6c360 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Thu, 25 Apr 2024 14:40:27 +0700
Subject: [PATCH 05/18] fix: restrict parallel config writes
---
MusicX/Services/ConfigService.cs | 30 +++++++++++++++++++++++++-----
1 file changed, 25 insertions(+), 5 deletions(-)
diff --git a/MusicX/Services/ConfigService.cs b/MusicX/Services/ConfigService.cs
index 8b20fad9..cab82f12 100644
--- a/MusicX/Services/ConfigService.cs
+++ b/MusicX/Services/ConfigService.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Text.Json;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
using MusicX.Helpers;
@@ -12,6 +13,8 @@ namespace MusicX.Services
{
public class ConfigService
{
+ private static readonly Semaphore ConfigSemaphore = new(1, 1, "MusicX_ConfigSemaphore");
+
private readonly JsonSerializerOptions _configSerializerOptions = new(JsonSerializerDefaults.Web)
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
@@ -68,9 +71,17 @@ public async Task GetConfig()
ConfigModel? config = null;
if(File.Exists(_configPath))
{
- await using var stream = File.OpenRead(_configPath);
-
- config = await JsonSerializer.DeserializeAsync(stream, _configSerializerOptions);
+ await ConfigSemaphore.WaitOneAsync();
+
+ try
+ {
+ await using var stream = File.OpenRead(_configPath);
+ config = await JsonSerializer.DeserializeAsync(stream, _configSerializerOptions);
+ }
+ finally
+ {
+ ConfigSemaphore.Release();
+ }
}
if (config is null)
@@ -83,9 +94,18 @@ public async Task GetConfig()
public async Task SetConfig(ConfigModel config)
{
Config = config;
+
+ await ConfigSemaphore.WaitOneAsync();
- await using var stream = File.Create(_configPath);
- await JsonSerializer.SerializeAsync(stream, config, _configSerializerOptions);
+ try
+ {
+ await using var stream = File.Create(_configPath);
+ await JsonSerializer.SerializeAsync(stream, config, _configSerializerOptions);
+ }
+ finally
+ {
+ ConfigSemaphore.Release();
+ }
}
}
}
From 2e2e1f44dd55fd643db35d81b75f9db03a0150d3 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Fri, 26 Apr 2024 14:46:56 +0700
Subject: [PATCH 06/18] add check updates button back
---
MusicX/RootWindow.xaml.cs | 26 +++----------------
MusicX/Services/UpdateService.cs | 40 +++++++++++++++++++++++++++++
MusicX/Views/SettingsView.xaml | 9 +++++++
MusicX/Views/SettingsView.xaml.cs | 21 +++++----------
MusicX/Views/StartingWindow.xaml.cs | 1 +
5 files changed, 60 insertions(+), 37 deletions(-)
create mode 100644 MusicX/Services/UpdateService.cs
diff --git a/MusicX/RootWindow.xaml.cs b/MusicX/RootWindow.xaml.cs
index 224aeed8..cad5b5ad 100644
--- a/MusicX/RootWindow.xaml.cs
+++ b/MusicX/RootWindow.xaml.cs
@@ -458,30 +458,10 @@ private async Task CheckUpdatesInStart()
try
{
await Task.Delay(2000);
- /*var github = StaticService.Container.GetRequiredService();
-
- var release = await github.GetLastRelease();
-
- if (release.TagName != StaticService.Version)
- navigationService.OpenModal(release);*/
-
- var config = await configService.GetConfig();
-
- var getBetaUpdates = config.GetBetaUpdates.GetValueOrDefault(false);
- var manager = new UpdateManager(new GithubSource("https://github.com/Fooxboy/MusicX-WPF",
- string.Empty, getBetaUpdates, new HttpClientFileDownloader()), new()
- {
- ExplicitChannel = getBetaUpdates ? "win-beta" : "win"
- });
-
- var updateInfo = await manager.CheckForUpdatesAsync();
- if (updateInfo is null)
- return;
-
- var viewModel = new AvailableNewUpdateModalViewModel(manager, updateInfo);
-
- navigationService.OpenModal(viewModel);
+ var updateService = StaticService.Container.GetRequiredService();
+
+ await updateService.CheckForUpdates();
}catch(Exception ex)
{
var properties = new Dictionary
diff --git a/MusicX/Services/UpdateService.cs b/MusicX/Services/UpdateService.cs
new file mode 100644
index 00000000..9b44462c
--- /dev/null
+++ b/MusicX/Services/UpdateService.cs
@@ -0,0 +1,40 @@
+using System.Threading.Tasks;
+using MusicX.ViewModels.Modals;
+using MusicX.Views.Modals;
+using Velopack;
+using Velopack.Sources;
+
+namespace MusicX.Services;
+
+public class UpdateService
+{
+ private readonly ConfigService _configService;
+ private readonly NavigationService _navigationService;
+
+ public UpdateService(ConfigService configService, NavigationService navigationService)
+ {
+ _configService = configService;
+ _navigationService = navigationService;
+ }
+
+ public async Task CheckForUpdates()
+ {
+ var getBetaUpdates = _configService.Config.GetBetaUpdates.GetValueOrDefault();
+ var manager = new UpdateManager(new GithubSource("https://github.com/Fooxboy/MusicX-WPF",
+ string.Empty, getBetaUpdates, new HttpClientFileDownloader()), new()
+ {
+ ExplicitChannel = getBetaUpdates ? "win-beta" : "win"
+ });
+
+ var updateInfo = await manager.CheckForUpdatesAsync();
+
+ if (updateInfo is null)
+ return false;
+
+ var viewModel = new AvailableNewUpdateModalViewModel(manager, updateInfo);
+
+ _navigationService.OpenModal(viewModel);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/MusicX/Views/SettingsView.xaml b/MusicX/Views/SettingsView.xaml
index 29115330..16bcc7ca 100644
--- a/MusicX/Views/SettingsView.xaml
+++ b/MusicX/Views/SettingsView.xaml
@@ -297,6 +297,15 @@
Opacity="0.6"
Text="26 марта 2022" />
+
+
();
+#if !DEBUG
try
{
- var navigation = StaticService.Container.GetRequiredService();
- var github = StaticService.Container.GetRequiredService();
-
- var release = await github.GetLastRelease();
-
-
-
- if (release.TagName == StaticService.Version)
+ var updateService = StaticService.Container.GetRequiredService();
+
+ if (!await updateService.CheckForUpdates())
{
snackbarService.Show("Уже обновлено!",
"У Вас установлена последняя версия MusicX! Обновлений пока что нет");
-
- }
- else
- {
- navigation.OpenModal(release);
}
}
catch (Exception ex)
@@ -237,7 +228,9 @@ private async void CheckUpdates_Click(object sender, RoutedEventArgs e)
snackbarService.Show("Ошибка", "Произошла ошибка при проверке обновлений");
}
-
+#else
+ snackbarService.Show("В режиме отладки", "Сервис обновлений отключен");
+#endif
}
private void TelegramButton_Click(object sender, RoutedEventArgs e)
diff --git a/MusicX/Views/StartingWindow.xaml.cs b/MusicX/Views/StartingWindow.xaml.cs
index 1e022d8e..345e8e50 100644
--- a/MusicX/Views/StartingWindow.xaml.cs
+++ b/MusicX/Views/StartingWindow.xaml.cs
@@ -112,6 +112,7 @@ await Task.Run(async () =>
collection.AddSingleton();
collection.AddSingleton();
collection.AddSingleton();
+ collection.AddSingleton();
var container = StaticService.Container = collection.BuildServiceProvider();
From 6985b5f93a6f3490ac1719cb52b90fe7421ba3f2 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Fri, 26 Apr 2024 14:57:52 +0700
Subject: [PATCH 07/18] fix: reference counting on winrt decoder
---
MusicX/Services/Player/Sources/BoomMediaSource.cs | 4 ++--
MusicX/Services/Player/Sources/MediaSourceBase.cs | 3 ---
MusicX/Services/Player/Sources/VkMediaSource.cs | 4 ++--
3 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/MusicX/Services/Player/Sources/BoomMediaSource.cs b/MusicX/Services/Player/Sources/BoomMediaSource.cs
index 28018123..e15c3ff9 100644
--- a/MusicX/Services/Player/Sources/BoomMediaSource.cs
+++ b/MusicX/Services/Player/Sources/BoomMediaSource.cs
@@ -37,9 +37,9 @@ public override async Task OpenWithMediaPlayerAsync(MediaPlayer player, Pl
["headers"] = $"Authorization: {_boomService.Client.DefaultRequestHeaders.Authorization}"
}, cancellationToken);
- RegisterSourceObjectReference(player, rtMediaSource);
-
await rtMediaSource.OpenWithMediaPlayerAsync(player).AsTask(cancellationToken);
+
+ RegisterSourceObjectReference(player, rtMediaSource);
}
catch (Exception e)
{
diff --git a/MusicX/Services/Player/Sources/MediaSourceBase.cs b/MusicX/Services/Player/Sources/MediaSourceBase.cs
index 8f93912c..28689eaf 100644
--- a/MusicX/Services/Player/Sources/MediaSourceBase.cs
+++ b/MusicX/Services/Player/Sources/MediaSourceBase.cs
@@ -194,9 +194,6 @@ protected static void RegisterSourceObjectReference(MediaPlayer player, IWinRTOb
void PlayerOnSourceChanged(MediaPlayer sender, object args)
{
- if (!ReferenceEquals(player.Source, rtObject))
- return;
-
player.SourceChanged -= PlayerOnSourceChanged;
if (rtObject is IDisposable disposable)
diff --git a/MusicX/Services/Player/Sources/VkMediaSource.cs b/MusicX/Services/Player/Sources/VkMediaSource.cs
index 6a6b5cc0..d4e9dfb6 100644
--- a/MusicX/Services/Player/Sources/VkMediaSource.cs
+++ b/MusicX/Services/Player/Sources/VkMediaSource.cs
@@ -26,10 +26,10 @@ public override async Task OpenWithMediaPlayerAsync(MediaPlayer player, Pl
try
{
var rtMediaSource = await CreateWinRtMediaSource(vkData, cancellationToken: cancellationToken);
-
- RegisterSourceObjectReference(player, rtMediaSource);
await rtMediaSource.OpenWithMediaPlayerAsync(player).AsTask(cancellationToken);
+
+ RegisterSourceObjectReference(player, rtMediaSource);
}
catch (Exception e)
{
From 9f1ab0c6b89da4e1aaf53dbd700044c9650dba69 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Fri, 26 Apr 2024 15:33:16 +0700
Subject: [PATCH 08/18] save last player last across restarts
---
MusicX/Models/ConfigModel.cs | 5 ++
MusicX/RootWindow.xaml.cs | 60 ++++++++++---------
MusicX/Services/Player/PlayerService.cs | 16 ++---
.../AvailableNewUpdateModalViewModel.cs | 9 ++-
MusicX/Views/SettingsView.xaml | 13 ++++
MusicX/Views/SettingsView.xaml.cs | 8 +++
MusicX/Views/StartingWindow.xaml.cs | 7 ---
7 files changed, 72 insertions(+), 46 deletions(-)
diff --git a/MusicX/Models/ConfigModel.cs b/MusicX/Models/ConfigModel.cs
index ed3b180d..5b83a412 100644
--- a/MusicX/Models/ConfigModel.cs
+++ b/MusicX/Models/ConfigModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using MusicX.Services.Player;
namespace MusicX.Models
{
@@ -53,5 +54,9 @@ public class ConfigModel
public string? DeviceId { get; set; }
public string? ExchangeToken { get; set; }
+
+ public bool? SavePlayerState { get; set; }
+
+ public PlayerState? LastPlayerState { get; set; }
}
}
diff --git a/MusicX/RootWindow.xaml.cs b/MusicX/RootWindow.xaml.cs
index 224aeed8..c5f9ca6f 100644
--- a/MusicX/RootWindow.xaml.cs
+++ b/MusicX/RootWindow.xaml.cs
@@ -64,8 +64,6 @@ public RootWindow(NavigationService navigationService, VkService vkService, Logg
playerSerivce.TrackChangedEvent += PlayerSerivce_TrackChangedEvent;
- Closing += RootWindow_Closing;
-
SingleAppService.Instance.RunWitchArgs += Instance_RunWitchArgs;
togetherService.ConnectedToSession += TogetherServiceOnConnectedToSession;
@@ -125,29 +123,6 @@ await Task.Factory.StartNew(async() =>
}
- private async void RootWindow_Closing(object? sender, CancelEventArgs e)
- {
- try
- {
- var listenTogetherService = StaticService.Container.GetRequiredService();
-
- if (listenTogetherService.IsConnectedToServer && listenTogetherService.PlayerMode != PlayerMode.None)
- {
- if (listenTogetherService.PlayerMode == PlayerMode.Owner)
- {
- await listenTogetherService.StopPlaySessionAsync();
- }
- else
- {
- await listenTogetherService.LeavePlaySessionAsync();
- }
- }
- }catch(Exception ex)
- {
- //nothing
- }
-
- }
private void PlayerSerivce_TrackChangedEvent(object? sender, EventArgs e)
{
if (PlayerShowed) return;
@@ -316,6 +291,13 @@ private async void Window_Loaded(object sender, RoutedEventArgs e)
if (config.NotifyMessages is null)
config.NotifyMessages = new() { ShowListenTogetherModal = true, LastShowedTelegramBlock = null };
+ if (config.LastPlayerState is not null)
+ {
+ await playerControl.PlayerService.RestoreFromStateAsync(config.LastPlayerState);
+
+ config.LastPlayerState = null;
+ }
+
await configService.SetConfig(config);
if(config.NotifyMessages.ShowListenTogetherModal)
@@ -575,11 +557,35 @@ private void NotifyIcon_LeftClick(NotifyIcon sender, RoutedEventArgs e)
}*/
- private void RootWindow_OnClosing(object? sender, CancelEventArgs e)
+ private async void RootWindow_OnClosing(object? sender, CancelEventArgs e)
{
configService.Config.Width = Width;
configService.Config.Height = Height;
- configService.SetConfig(configService.Config).SafeFireAndForget(continueOnCapturedContext: true);
+
+ if (configService.Config.SavePlayerState is true)
+ configService.Config.LastPlayerState = PlayerState.CreateOrNull(playerControl.PlayerService);
+
+ await configService.SetConfig(configService.Config);
+
+ try
+ {
+ var listenTogetherService = StaticService.Container.GetRequiredService();
+
+ if (listenTogetherService.IsConnectedToServer && listenTogetherService.PlayerMode != PlayerMode.None)
+ {
+ if (listenTogetherService.PlayerMode == PlayerMode.Owner)
+ {
+ await listenTogetherService.StopPlaySessionAsync();
+ }
+ else
+ {
+ await listenTogetherService.LeavePlaySessionAsync();
+ }
+ }
+ }catch(Exception ex)
+ {
+ //nothing
+ }
}
private void RootFrame_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
diff --git a/MusicX/Services/Player/PlayerService.cs b/MusicX/Services/Player/PlayerService.cs
index ce52de0a..b2450410 100644
--- a/MusicX/Services/Player/PlayerService.cs
+++ b/MusicX/Services/Player/PlayerService.cs
@@ -89,8 +89,7 @@ public PlayerService(Logger logger, ISnackbarService snackbarService,
public async Task RestoreFromStateAsync(PlayerState state)
{
- await PlayAsync(state.Playlist, state.Track);
- Seek(state.Position);
+ await PlayAsync(state.Playlist, state.Track, state.Position);
}
public async void Play()
@@ -118,12 +117,13 @@ await Task.WhenAll(
_statsListeners.Select(b => b.TrackChangedAsync(previousTrack, CurrentTrack!, ChangeReason.TrackChange)));
}
- private async Task PlayTrackAsync(PlaylistTrack track)
+ private async Task PlayTrackAsync(PlaylistTrack track, TimeSpan? position = null)
{
try
{
if (CurrentTrack == track)
{
+ if (position is not null) Seek(position.Value);
player.Play();
return;
}
@@ -172,6 +172,8 @@ private async Task PlayTrackAsync(PlaylistTrack track)
await NextTrack();
return;
}
+
+ if (position is not null) Seek(position.Value);
player.Play();
UpdateWindowsData().SafeFireAndForget();
@@ -195,12 +197,12 @@ private async Task PlayTrackAsync(PlaylistTrack track)
}
- public async Task PlayAsync(IPlaylist playlist, PlaylistTrack? firstTrack = null)
+ public async Task PlayAsync(IPlaylist playlist, PlaylistTrack? firstTrack = null, TimeSpan? startPosition = null)
{
if(_listenTogetherService.PlayerMode == PlayerMode.Listener)
{
await _listenTogetherService.LeavePlaySessionAsync();
- await Application.Current.Dispatcher.InvokeAsync(() => PlayAsync(playlist).SafeFireAndForget());
+ await Application.Current.Dispatcher.InvokeAsync(() => PlayAsync(playlist, firstTrack, startPosition).SafeFireAndForget());
return;
}
@@ -237,7 +239,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
});
}
- firstTrackTask = PlayTrackAsync(firstTrack);
+ firstTrackTask = PlayTrackAsync(firstTrack, startPosition);
await Task.WhenAll(
_statsListeners.Select(b => b.TrackChangedAsync(CurrentTrack, firstTrack, ChangeReason.PlaylistChange)));
@@ -260,7 +262,7 @@ await Task.WhenAll(
if (firstTrack is null)
{
var previousTrack = CurrentTrack;
- await PlayTrackAsync(Tracks[0]);
+ await PlayTrackAsync(Tracks[0], startPosition);
await Task.WhenAll(
_statsListeners.Select(b => b.TrackChangedAsync(previousTrack, CurrentTrack!, ChangeReason.PlaylistChange)));
diff --git a/MusicX/ViewModels/Modals/AvailableNewUpdateModalViewModel.cs b/MusicX/ViewModels/Modals/AvailableNewUpdateModalViewModel.cs
index 3740d49c..e695f595 100644
--- a/MusicX/ViewModels/Modals/AvailableNewUpdateModalViewModel.cs
+++ b/MusicX/ViewModels/Modals/AvailableNewUpdateModalViewModel.cs
@@ -48,12 +48,11 @@ private async Task Execute()
await _updateManager.DownloadUpdatesAsync(UpdateInfo, ProgressHandler);
var playerState = PlayerState.CreateOrNull(StaticService.Container.GetRequiredService());
+ var configService = StaticService.Container.GetRequiredService();
- _updateManager.WaitExitThenApplyUpdates(UpdateInfo, restartArgs: new []
- {
- "--play",
- playerState is null ? "null" : JsonSerializer.Serialize(playerState)
- });
+ configService.Config.LastPlayerState = playerState;
+
+ _updateManager.WaitExitThenApplyUpdates(UpdateInfo);
Application.Current.Shutdown();
}
diff --git a/MusicX/Views/SettingsView.xaml b/MusicX/Views/SettingsView.xaml
index 29115330..bea7c027 100644
--- a/MusicX/Views/SettingsView.xaml
+++ b/MusicX/Views/SettingsView.xaml
@@ -261,6 +261,19 @@
FontSize="18"
Unchecked="MinimizeToTray_Unchecked" />
+
+
+
+
+
diff --git a/MusicX/Views/SettingsView.xaml.cs b/MusicX/Views/SettingsView.xaml.cs
index f6fd6574..39ebc129 100644
--- a/MusicX/Views/SettingsView.xaml.cs
+++ b/MusicX/Views/SettingsView.xaml.cs
@@ -96,6 +96,7 @@ private async void SettingsView_Loaded(object sender, RoutedEventArgs e)
WinterTheme.IsChecked = config.WinterTheme.Value;
MinimizeToTray.IsChecked = config.MinimizeToTray.Value;
GetBetaUpdates.IsChecked = config.GetBetaUpdates.Value;
+ SavePlayerState.IsChecked = config.SavePlayerState.GetValueOrDefault();
UserName.Text = config.UserName;
@@ -586,5 +587,12 @@ private void CatalogsCard_Click(object sender, RoutedEventArgs e)
{
StaticService.Container.GetRequiredService().OpenSection("profiles");
}
+
+ private async void SavePlayerState_OnCheckChanged(object sender, RoutedEventArgs e)
+ {
+ config.SavePlayerState = SavePlayerState.IsChecked;
+
+ await configService.SetConfig(config);
+ }
}
}
diff --git a/MusicX/Views/StartingWindow.xaml.cs b/MusicX/Views/StartingWindow.xaml.cs
index 1e022d8e..a0c5deaa 100644
--- a/MusicX/Views/StartingWindow.xaml.cs
+++ b/MusicX/Views/StartingWindow.xaml.cs
@@ -186,13 +186,6 @@ await container.GetRequiredService()
{
await rootWindow.StartListenTogether(arg[1]);
}
-
- var playArgIndex = Array.BinarySearch(_args, "--play",
- StringComparer.OrdinalIgnoreCase);
- if (playArgIndex >= 0 && playArgIndex + 1 < _args.Length &&
- JsonSerializer.Deserialize(string.Join(string.Empty, _args[(playArgIndex + 1)..])) is { } state)
- await container.GetRequiredService()
- .RestoreFromStateAsync(state);
}
this.Close();
From b01889872a2fdcb9217e19315feec54b8bcb8e9a Mon Sep 17 00:00:00 2001
From: Fooxboy
Date: Fri, 26 Apr 2024 15:56:21 +0300
Subject: [PATCH 09/18] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?=
=?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9=D0=BD=20?=
=?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
MusicX/Controls/BlockControl.xaml | 2 +-
MusicX/Controls/Blocks/TitleBlockControl.xaml | 4 +-
MusicX/Controls/PlayerControl.xaml.cs | 4 +-
MusicX/Views/SettingsView.xaml | 108 +++++++++++-------
MusicX/Views/SettingsView.xaml.cs | 9 ++
5 files changed, 82 insertions(+), 45 deletions(-)
diff --git a/MusicX/Controls/BlockControl.xaml b/MusicX/Controls/BlockControl.xaml
index 8abb4d39..06d2abba 100644
--- a/MusicX/Controls/BlockControl.xaml
+++ b/MusicX/Controls/BlockControl.xaml
@@ -31,7 +31,7 @@
-
+
diff --git a/MusicX/Controls/Blocks/TitleBlockControl.xaml b/MusicX/Controls/Blocks/TitleBlockControl.xaml
index 288b731d..93961d5e 100644
--- a/MusicX/Controls/Blocks/TitleBlockControl.xaml
+++ b/MusicX/Controls/Blocks/TitleBlockControl.xaml
@@ -24,7 +24,7 @@
Style="{StaticResource HeaderHyperlinkButtonStyle}"
FontFamily="{StaticResource VKSansDemiBold}"
FontSize="30"
- FontWeight="DemiBold"
+ FontWeight="Bold"
Click="MoreButton_Click"
Content="{TemplateBinding Content}">
@@ -36,7 +36,7 @@
diff --git a/MusicX/Controls/PlayerControl.xaml.cs b/MusicX/Controls/PlayerControl.xaml.cs
index 4f7a860b..7b4b1cb0 100644
--- a/MusicX/Controls/PlayerControl.xaml.cs
+++ b/MusicX/Controls/PlayerControl.xaml.cs
@@ -286,8 +286,8 @@ private void UpdateSpeakerIcon()
{
_ when PlayerService.IsMuted => new SymbolIcon(SymbolRegular.SpeakerOff28),
0.0 => new SymbolIcon(SymbolRegular.SpeakerOff28),
- > 0.0 and < 0.30 => new SymbolIcon(SymbolRegular.Speaker032),
- > 0.30 and < 0.60 => new SymbolIcon(SymbolRegular.Speaker132),
+ > 0.0 and < 0.10 => new SymbolIcon(SymbolRegular.Speaker032),
+ > 0.10 and < 0.60 => new SymbolIcon(SymbolRegular.Speaker132),
> 0.80 => new SymbolIcon(SymbolRegular.Speaker232),
_ => SpeakerIcon.Icon
};
diff --git a/MusicX/Views/SettingsView.xaml b/MusicX/Views/SettingsView.xaml
index bea7c027..65de18a0 100644
--- a/MusicX/Views/SettingsView.xaml
+++ b/MusicX/Views/SettingsView.xaml
@@ -83,37 +83,44 @@
FontSize="30"
Text="Настройки" />
+
-
+
+
+
+
-
+
Загрузка треков
-
+
+
+
+
+
-
-
+
+
-
+
+
+
-
-
+
+
+
+
-
+
Логи
-
+
+
+
+
+
-
+
+
+
-
-
+
+
-
+
+
+
-
+
@@ -331,7 +355,7 @@
Height="30"
Margin="10,0,0,0"
Appearance="Secondary"
- Click="TelegramButton_Click"
+ Click="TelegramChat_Click"
Content="Телеграм чат" />
@@ -366,6 +390,9 @@
NavigateUri="https://github.com/lepoco/wpfui/blob/main/LICENSE" />
+
+
+
@@ -383,6 +410,9 @@
NavigateUri="https://t.me/VkDotNet" />
+
+
+
@@ -396,6 +426,9 @@
NavigateUri="https://github.com/flowersne/VkNet.AudioBypass/blob/master/LICENSE" />
+
+
+
@@ -405,6 +438,8 @@
NavigateUri="https://ffmpeg.org/" />
+
+
@@ -418,6 +453,8 @@
NavigateUri="https://github.com/cmxl/FFmpeg.NET/blob/master/LICENSE.md" />
+
+
@@ -431,15 +468,6 @@
NavigateUri="https://github.com/Lachee/discord-rpc-csharp/blob/master/LICENSE" />
-
-
-
-
-
-
diff --git a/MusicX/Views/SettingsView.xaml.cs b/MusicX/Views/SettingsView.xaml.cs
index 39ebc129..0d3e7768 100644
--- a/MusicX/Views/SettingsView.xaml.cs
+++ b/MusicX/Views/SettingsView.xaml.cs
@@ -250,6 +250,15 @@ private void TelegramButton_Click(object sender, RoutedEventArgs e)
});
}
+ private void TelegramChat_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = "https://t.me/+lO37psdwX2s3NjZi",
+ UseShellExecute = true
+ });
+ }
+
private void OpenLogs_Click(object sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo
From 792ce1f68ec0b42450886ad702c2a6aba79136b0 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Thu, 25 Apr 2024 15:20:41 +0700
Subject: [PATCH 10/18] implement hide to tray
---
MusicX/MusicX.csproj | 2 +
MusicX/RootWindow.xaml | 6 +++
MusicX/RootWindow.xaml.cs | 66 +++++++++++++++++++----
MusicX/Views/SettingsView.xaml.cs | 35 ++----------
MusicX/packages.lock.json | 89 ++++++++++++++++++-------------
5 files changed, 119 insertions(+), 79 deletions(-)
diff --git a/MusicX/MusicX.csproj b/MusicX/MusicX.csproj
index b207a5ac..3d6a016b 100644
--- a/MusicX/MusicX.csproj
+++ b/MusicX/MusicX.csproj
@@ -86,6 +86,7 @@
+
@@ -102,6 +103,7 @@
+
diff --git a/MusicX/RootWindow.xaml b/MusicX/RootWindow.xaml
index 15f2ae30..7629bacf 100644
--- a/MusicX/RootWindow.xaml
+++ b/MusicX/RootWindow.xaml
@@ -8,6 +8,7 @@
xmlns:wpfui="clr-namespace:Wpf.Ui.Controls;assembly=Wpf.Ui"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:views="clr-namespace:MusicX.Views"
+ xmlns:tray="http://schemas.lepo.co/wpfui/2022/xaml/tray"
d:DesignHeight="720"
d:DesignWidth="1280"
KeyDown="Window_KeyDown"
@@ -135,6 +136,11 @@
+
diff --git a/MusicX/RootWindow.xaml.cs b/MusicX/RootWindow.xaml.cs
index 4ac8db06..23e1e854 100644
--- a/MusicX/RootWindow.xaml.cs
+++ b/MusicX/RootWindow.xaml.cs
@@ -11,6 +11,7 @@
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Toolkit.Uwp.Notifications;
using MusicX.Controls;
using MusicX.Core.Models;
using MusicX.Core.Services;
@@ -28,6 +29,7 @@
using Wpf.Ui;
using Wpf.Ui.Controls;
using Wpf.Ui.Extensions;
+using Wpf.Ui.Tray.Controls;
using NavigationService = MusicX.Services.NavigationService;
namespace MusicX
@@ -54,6 +56,7 @@ public RootWindow(NavigationService navigationService, VkService vkService, Logg
ConfigService configService, ISnackbarService snackbarService,
ListenTogetherService togetherService) : base(snackbarService, navigationService, logger)
{
+ Application.Current.MainWindow = this;
InitializeComponent();
this.navigationService = navigationService;
this.vkService = vkService;
@@ -72,6 +75,14 @@ public RootWindow(NavigationService navigationService, VkService vkService, Logg
Height = configService.Config.Height;
}
+ protected override void OnStateChanged(EventArgs e)
+ {
+ base.OnStateChanged(e);
+
+ if (WindowState == WindowState.Minimized && TrayIcon.IsRegistered && ConsiderHideToTray())
+ Hide();
+ }
+
protected override SnackbarPresenter? GetSnackbarPresenter() => RootSnackbar;
private async Task TogetherServiceOnConnectedToSession(PlaylistTrack arg)
@@ -123,6 +134,44 @@ await Task.Factory.StartNew(async() =>
}
+ private bool ConsiderHideToTray()
+ {
+ if (configService.Config.MinimizeToTray is not null) return configService.Config.MinimizeToTray.Value;
+
+ new ToastContentBuilder()
+ .AddText("Приложене было скрыто в трее, нажмите на иконку, чтобы снова открыть окно.")
+ .AddText("Поведение можно изменить в настройках. Это уведомление больше не будет показано.")
+ .Show();
+
+ configService.Config.MinimizeToTray = true;
+ configService.SetConfig(configService.Config).SafeFireAndForget();
+
+ return true;
+ }
+
+ private async void RootWindow_Closing(object? sender, CancelEventArgs e)
+ {
+ try
+ {
+ var listenTogetherService = StaticService.Container.GetRequiredService();
+
+ if (listenTogetherService.IsConnectedToServer && listenTogetherService.PlayerMode != PlayerMode.None)
+ {
+ if (listenTogetherService.PlayerMode == PlayerMode.Owner)
+ {
+ await listenTogetherService.StopPlaySessionAsync();
+ }
+ else
+ {
+ await listenTogetherService.LeavePlaySessionAsync();
+ }
+ }
+ }catch(Exception ex)
+ {
+ //nothing
+ }
+
+ }
private void PlayerSerivce_TrackChangedEvent(object? sender, EventArgs e)
{
if (PlayerShowed) return;
@@ -305,18 +354,7 @@ private async void Window_Loaded(object sender, RoutedEventArgs e)
navigationService.OpenModal();
}
- /*if(config.MinimizeToTray != null) // TODO tray
- {
- WpfTitleBar.MinimizeToTray = config.MinimizeToTray.Value;
- }else
- {
- WpfTitleBar.MinimizeToTray = false;
- }*/
-
this.WindowState = WindowState.Normal;
-
-
- // AppNotifyIcon.Register();
}
catch (Exception ex)
{
@@ -574,5 +612,11 @@ private void RootFrame_Navigating(object sender, System.Windows.Navigation.Navig
? Visibility.Visible
: Visibility.Collapsed;
}
+
+ private void TrayIcon_OnLeftClick(NotifyIcon sender, RoutedEventArgs e)
+ {
+ Show();
+ WindowState = WindowState.Normal;
+ }
}
}
diff --git a/MusicX/Views/SettingsView.xaml.cs b/MusicX/Views/SettingsView.xaml.cs
index d1f1e19f..326e2f8b 100644
--- a/MusicX/Views/SettingsView.xaml.cs
+++ b/MusicX/Views/SettingsView.xaml.cs
@@ -65,37 +65,12 @@ private async void SettingsView_Loaded(object sender, RoutedEventArgs e)
this.config = await configService.GetConfig();
- if (config.ShowRPC == null)
- {
- config.ShowRPC = true;
- }
-
- if(config.BroadcastVK == null)
- {
- config.BroadcastVK = false;
- }
-
- if(config.WinterTheme == null)
- {
- config.WinterTheme = false;
- }
-
- if (config.MinimizeToTray == null)
- {
- config.MinimizeToTray = false;
- }
-
- if (config.GetBetaUpdates == null)
- {
- config.GetBetaUpdates = false;
- }
-
- ShowRPC.IsChecked = config.ShowRPC.Value;
- BroacastVK.IsChecked = config.BroadcastVK.Value;
+ ShowRPC.IsChecked = config.ShowRPC.GetValueOrDefault();
+ BroacastVK.IsChecked = config.BroadcastVK.GetValueOrDefault();
ShowAmimatedBackground.IsChecked = config.AnimatedBackground;
- WinterTheme.IsChecked = config.WinterTheme.Value;
- MinimizeToTray.IsChecked = config.MinimizeToTray.Value;
- GetBetaUpdates.IsChecked = config.GetBetaUpdates.Value;
+ WinterTheme.IsChecked = config.WinterTheme.GetValueOrDefault();
+ MinimizeToTray.IsChecked = config.MinimizeToTray.GetValueOrDefault();
+ GetBetaUpdates.IsChecked = config.GetBetaUpdates.GetValueOrDefault();
SavePlayerState.IsChecked = config.SavePlayerState.GetValueOrDefault();
UserName.Text = config.UserName;
diff --git a/MusicX/packages.lock.json b/MusicX/packages.lock.json
index 935ad6a2..7324859d 100644
--- a/MusicX/packages.lock.json
+++ b/MusicX/packages.lock.json
@@ -55,6 +55,18 @@
"Microsoft.AppCenter": "5.0.3"
}
},
+ "Microsoft.Toolkit.Uwp.Notifications": {
+ "type": "Direct",
+ "requested": "[7.1.3, )",
+ "resolved": "7.1.3",
+ "contentHash": "A1dglAzb24gjehmb7DwGd07mfyZ1gacAK7ObE0KwDlRc3mayH2QW7cSOy3TkkyELjLg19OQBuhPOj4SpXET9lg==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "4.7.0",
+ "System.Drawing.Common": "4.7.0",
+ "System.Reflection.Emit": "4.7.0",
+ "System.ValueTuple": "4.5.0"
+ }
+ },
"Microsoft.VCRTForwarders.140": {
"type": "Direct",
"requested": "[1.0.8-pre, )",
@@ -144,6 +156,16 @@
"resolved": "3.0.4",
"contentHash": "Jbt8nJ4MSC/WBhqx6iXOW06Rt2UUNVxA8+23AOSM63jlOIhU6e6P4BIw8rL/UnRPycOdA2vntYl5i7k53E0AGg=="
},
+ "WPF-UI.Tray": {
+ "type": "Direct",
+ "requested": "[3.0.4, )",
+ "resolved": "3.0.4",
+ "contentHash": "cYa4eVZqrvRoSXtGgww6yLoSHaGCvSYEfNHDBC/iyv/z8pyWEpwN9EQnJRgGW2AclnG0zrOlhp1aLhoeFkY5qw==",
+ "dependencies": {
+ "System.Drawing.Common": "8.0.0",
+ "WPF-UI": "3.0.4"
+ }
+ },
"WpfScreenHelper": {
"type": "Direct",
"requested": "[2.1.0, )",
@@ -359,24 +381,19 @@
"resolved": "8.0.0",
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
},
- "Microsoft.NETCore.Platforms": {
- "type": "Transitive",
- "resolved": "2.0.0",
- "contentHash": "VdLJOCXhZaEMY7Hm2GKiULmn7IEPFE4XC5LPSfBVCUIA8YLZVh846gtfBJalsPQF2PlzdD7ecX7DZEulJ402ZQ=="
- },
"Microsoft.Win32.Registry": {
"type": "Transitive",
- "resolved": "4.5.0",
- "contentHash": "+FWlwd//+Tt56316p00hVePBCouXyEzT86Jb3+AuRotTND0IYn0OO3obs1gnQEs/txEnt+rF2JBGLItTG+Be6A==",
+ "resolved": "4.7.0",
+ "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==",
"dependencies": {
- "System.Security.AccessControl": "4.5.0",
- "System.Security.Principal.Windows": "4.5.0"
+ "System.Security.AccessControl": "4.7.0",
+ "System.Security.Principal.Windows": "4.7.0"
}
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A=="
+ "resolved": "8.0.0",
+ "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw=="
},
"Microsoft.Windows.CsWinRT": {
"type": "Transitive",
@@ -488,10 +505,10 @@
},
"System.Drawing.Common": {
"type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==",
+ "resolved": "8.0.0",
+ "contentHash": "JkbHJjtI/dWc5dfmEdJlbe3VwgZqCkZRtfuWFh5GOv0f+gGCfBtzMpIVkmdkj2AObO9y+oiOi81UGwH3aBYuqA==",
"dependencies": {
- "Microsoft.Win32.SystemEvents": "6.0.0"
+ "Microsoft.Win32.SystemEvents": "8.0.0"
}
},
"System.IO.Pipelines": {
@@ -512,6 +529,11 @@
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
+ "System.Reflection.Emit": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ=="
+ },
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -538,11 +560,8 @@
},
"System.Security.Principal.Windows": {
"type": "Transitive",
- "resolved": "4.5.0",
- "contentHash": "U77HfRXlZlOeIXd//Yoj6Jnk8AXlbeisf1oq1os+hxOGVnuG+lGSfGqTwTZBoORFF6j/0q7HXIl8cqwQ9aUGqQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "2.0.0"
- }
+ "resolved": "4.7.0",
+ "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ=="
},
"System.Text.Encodings.Web": {
"type": "Transitive",
@@ -554,6 +573,11 @@
"resolved": "8.0.0",
"contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA=="
},
+ "System.ValueTuple": {
+ "type": "Transitive",
+ "resolved": "4.5.0",
+ "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
+ },
"System.Windows.Extensions": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -664,31 +688,23 @@
},
"Microsoft.Win32.Registry": {
"type": "Transitive",
- "resolved": "4.5.0",
- "contentHash": "+FWlwd//+Tt56316p00hVePBCouXyEzT86Jb3+AuRotTND0IYn0OO3obs1gnQEs/txEnt+rF2JBGLItTG+Be6A==",
+ "resolved": "4.7.0",
+ "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==",
"dependencies": {
- "System.Security.AccessControl": "4.5.0",
- "System.Security.Principal.Windows": "4.5.0"
+ "System.Security.AccessControl": "4.7.0",
+ "System.Security.Principal.Windows": "4.7.0"
}
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A=="
+ "resolved": "8.0.0",
+ "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw=="
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.5",
"contentHash": "Fqp/FQlb+USnEC2qfWOdsY4fFir3sob9BQMgdT3rcamUAoB7id8V0WknWdsFnE4TXBKDiM79+oPoZoHAuU/dsg=="
},
- "System.Drawing.Common": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==",
- "dependencies": {
- "Microsoft.Win32.SystemEvents": "6.0.0"
- }
- },
"System.Management": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -709,11 +725,8 @@
},
"System.Security.Principal.Windows": {
"type": "Transitive",
- "resolved": "4.5.0",
- "contentHash": "U77HfRXlZlOeIXd//Yoj6Jnk8AXlbeisf1oq1os+hxOGVnuG+lGSfGqTwTZBoORFF6j/0q7HXIl8cqwQ9aUGqQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "2.0.0"
- }
+ "resolved": "4.7.0",
+ "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ=="
},
"System.Text.Encodings.Web": {
"type": "Transitive",
From 0839e8610f5208fef8e57b5f76de3942f0201120 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Fri, 26 Apr 2024 20:26:21 +0700
Subject: [PATCH 11/18] remove unused method left from rebase
---
MusicX/RootWindow.xaml.cs | 23 -----------------------
1 file changed, 23 deletions(-)
diff --git a/MusicX/RootWindow.xaml.cs b/MusicX/RootWindow.xaml.cs
index 23e1e854..6feff5d2 100644
--- a/MusicX/RootWindow.xaml.cs
+++ b/MusicX/RootWindow.xaml.cs
@@ -149,29 +149,6 @@ private bool ConsiderHideToTray()
return true;
}
- private async void RootWindow_Closing(object? sender, CancelEventArgs e)
- {
- try
- {
- var listenTogetherService = StaticService.Container.GetRequiredService();
-
- if (listenTogetherService.IsConnectedToServer && listenTogetherService.PlayerMode != PlayerMode.None)
- {
- if (listenTogetherService.PlayerMode == PlayerMode.Owner)
- {
- await listenTogetherService.StopPlaySessionAsync();
- }
- else
- {
- await listenTogetherService.LeavePlaySessionAsync();
- }
- }
- }catch(Exception ex)
- {
- //nothing
- }
-
- }
private void PlayerSerivce_TrackChangedEvent(object? sender, EventArgs e)
{
if (PlayerShowed) return;
From b16550cf861a0befff3e3989cdb3fe0f2870bc37 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Fri, 26 Apr 2024 20:47:01 +0700
Subject: [PATCH 12/18] fix: regression, missing bottom offset on section view
---
MusicX/Views/SectionView.xaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MusicX/Views/SectionView.xaml b/MusicX/Views/SectionView.xaml
index 99078624..a20df5f5 100644
--- a/MusicX/Views/SectionView.xaml
+++ b/MusicX/Views/SectionView.xaml
@@ -123,7 +123,7 @@
IsInertiaEnabled="True"
ScrollChanged="SectionScrollViewer_ScrollChanged"
hc:ScrollViewerAttach.AutoHide="True">
-
+
From a0af51e5a3ab93d9df2fd9ac0936334c2451129e Mon Sep 17 00:00:00 2001
From: Fooxboy
Date: Fri, 26 Apr 2024 19:31:10 +0300
Subject: [PATCH 13/18] =?UTF-8?q?#373=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD?=
=?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9=D0=BD=20?=
=?UTF-8?q?=D0=BF=D0=BB=D0=B5=D0=B9=D0=BB=D0=B8=D1=81=D1=82=D0=BE=D0=B2=20?=
=?UTF-8?q?"=D0=9A=D0=B0=D0=BA=D0=BE=D0=B9=20=D1=81=D0=B5=D0=B9=D1=87?=
=?UTF-8?q?=D0=B0=D1=81=20=D0=B2=D0=B0=D0=B9=D0=B1"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
MusicX/Controls/BlockControl.xaml | 6 +
MusicX/Controls/CroppedPlaylistControl.xaml | 34 ++++
.../Controls/CroppedPlaylistControl.xaml.cs | 172 ++++++++++++++++++
MusicX/Controls/ListPlaylists.xaml | 12 +-
MusicX/Controls/ListPlaylists.xaml.cs | 9 +
5 files changed, 232 insertions(+), 1 deletion(-)
create mode 100644 MusicX/Controls/CroppedPlaylistControl.xaml
create mode 100644 MusicX/Controls/CroppedPlaylistControl.xaml.cs
diff --git a/MusicX/Controls/BlockControl.xaml b/MusicX/Controls/BlockControl.xaml
index 06d2abba..bdf781e2 100644
--- a/MusicX/Controls/BlockControl.xaml
+++ b/MusicX/Controls/BlockControl.xaml
@@ -12,6 +12,7 @@
xmlns:viewModels="clr-namespace:MusicX.ViewModels.Controls" xmlns:hc="https://handyorg.github.io/handycontrol"
d:DesignHeight="450"
d:DesignWidth="800"
+ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
ContentTemplateSelector="{DynamicResource BlockTemplateSelector}"
mc:Ignorable="d">
@@ -68,6 +69,11 @@
+
+
+
+
+
diff --git a/MusicX/Controls/CroppedPlaylistControl.xaml b/MusicX/Controls/CroppedPlaylistControl.xaml
new file mode 100644
index 00000000..8ed71f72
--- /dev/null
+++ b/MusicX/Controls/CroppedPlaylistControl.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MusicX/Controls/CroppedPlaylistControl.xaml.cs b/MusicX/Controls/CroppedPlaylistControl.xaml.cs
new file mode 100644
index 00000000..633260df
--- /dev/null
+++ b/MusicX/Controls/CroppedPlaylistControl.xaml.cs
@@ -0,0 +1,172 @@
+using Microsoft.AppCenter.Analytics;
+using Microsoft.AppCenter.Crashes;
+using Microsoft.Extensions.DependencyInjection;
+using MusicX.Core.Models;
+using MusicX.Core.Services;
+using MusicX.Services;
+using MusicX.Services.Player.Playlists;
+using MusicX.Services.Player;
+using MusicX.Views;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using Wpf.Ui.Controls;
+using System;
+
+namespace MusicX.Controls
+{
+ ///
+ /// Логика взаимодействия для CroppedPlaylistControl.xaml
+ ///
+ public partial class CroppedPlaylistControl : UserControl
+ {
+
+ public static readonly DependencyProperty TitleProperty =
+ DependencyProperty.Register("Title", typeof(string), typeof(CroppedPlaylistControl), new PropertyMetadata(string.Empty));
+
+ public string Title
+ {
+ get { return (string)GetValue(TitleProperty); }
+ set
+ {
+ SetValue(TitleProperty, value);
+ }
+ }
+
+ public static readonly DependencyProperty CoverProperty =
+ DependencyProperty.Register("Cover", typeof(string), typeof(CroppedPlaylistControl), new PropertyMetadata(string.Empty));
+
+ public string Cover
+ {
+ get { return (string)GetValue(CoverProperty); }
+ set
+ {
+ SetValue(CoverProperty, value);
+ }
+ }
+
+ public static readonly DependencyProperty PlaylistProperty =
+ DependencyProperty.Register("Playlist", typeof(Playlist), typeof(CroppedPlaylistControl), new PropertyMetadata(new Playlist()));
+
+ public Playlist Playlist
+ {
+ get { return (Playlist)GetValue(PlaylistProperty); }
+ set
+ {
+ SetValue(PlaylistProperty, value);
+ }
+ }
+
+ public CroppedPlaylistControl()
+ {
+ InitializeComponent();
+ }
+
+ private bool _nowPlay = false;
+
+ private void Border_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
+ {
+ if(sender is Border border)
+ {
+ border.Opacity = 1;
+ }
+ }
+
+ private void Border_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
+ {
+ if (sender is Border border)
+ {
+ border.Opacity = 0.6;
+ }
+ }
+
+ private void OpenFullPlaylist(object sender, System.Windows.Input.MouseButtonEventArgs e)
+ {
+ var notificationService = StaticService.Container.GetRequiredService();
+
+ notificationService.OpenExternalPage(new PlaylistView(Playlist));
+ }
+
+ private async void PlayPlaylist(object sender, System.Windows.Input.MouseButtonEventArgs e)
+ {
+ e.Handled = true;
+ try
+ {
+ var properties = new Dictionary
+ {
+#if DEBUG
+ { "IsDebug", "True" },
+#endif
+ {"Version", StaticService.Version }
+ };
+ Analytics.TrackEvent("PlayPlaylistWithButton", properties);
+
+ var playerService = StaticService.Container.GetRequiredService();
+
+ if (!_nowPlay)
+ {
+ _nowPlay = true;
+
+ PlayIcon.Symbol = SymbolRegular.Timer24;
+ var vkService = StaticService.Container.GetRequiredService();
+
+ await playerService.PlayAsync(
+ new VkPlaylistPlaylist(vkService, new(Playlist.Id, Playlist.OwnerId, Playlist.AccessKey)));
+
+ PlayIcon.Symbol = SymbolRegular.Pause24;
+
+ }
+ else
+ {
+ playerService.Pause();
+ PlayIcon.Symbol = SymbolRegular.Play24;
+
+ _nowPlay = false;
+ }
+ }
+ catch (Exception ex)
+ {
+
+ var properties = new Dictionary
+ {
+#if DEBUG
+ { "IsDebug", "True" },
+#endif
+ {"Version", StaticService.Version }
+ };
+ Crashes.TrackError(ex, properties);
+ }
+ }
+
+ private void UserControl_Loaded(object sender, RoutedEventArgs e)
+ {
+ var player = StaticService.Container.GetRequiredService();
+ player.CurrentPlaylistChanged += PlayerOnCurrentPlaylistChanged;
+ }
+
+ private void UserControl_Unloaded(object sender, RoutedEventArgs e)
+ {
+ var player = StaticService.Container.GetRequiredService();
+ player.CurrentPlaylistChanged -= PlayerOnCurrentPlaylistChanged;
+ }
+
+ private void PlayerOnCurrentPlaylistChanged(object? sender, EventArgs e)
+ {
+ if (sender is not PlayerService service)
+ return;
+
+ if (service.CurrentPlaylist is VkPlaylistPlaylist { Data: { } data } && data.PlaylistId == Playlist.Id)
+ {
+ _nowPlay = true;
+ PlayIcon.Symbol = SymbolRegular.Pause24;
+ }
+ else
+ {
+ _nowPlay = false;
+ PlayIcon.Symbol = SymbolRegular.Play24;
+ }
+ }
+
+ }
+}
diff --git a/MusicX/Controls/ListPlaylists.xaml b/MusicX/Controls/ListPlaylists.xaml
index 0feb8f0e..2b62a301 100644
--- a/MusicX/Controls/ListPlaylists.xaml
+++ b/MusicX/Controls/ListPlaylists.xaml
@@ -22,7 +22,11 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MusicX/Controls/ListPlaylists.xaml.cs b/MusicX/Controls/ListPlaylists.xaml.cs
index 89271dae..9957a7f9 100644
--- a/MusicX/Controls/ListPlaylists.xaml.cs
+++ b/MusicX/Controls/ListPlaylists.xaml.cs
@@ -22,5 +22,14 @@ public bool ShowFull
get => (bool)GetValue(ShowFullProperty);
set => SetValue(ShowFullProperty, value);
}
+
+ public static readonly DependencyProperty IsCroppedProperty = DependencyProperty.Register(
+ nameof(IsCropped), typeof(bool), typeof(ListPlaylists));
+
+ public bool IsCropped
+ {
+ get => (bool)GetValue(IsCroppedProperty);
+ set => SetValue(IsCroppedProperty, value);
+ }
}
}
From 0cf30d75d30c76b5da3ed698032c306358000e5e Mon Sep 17 00:00:00 2001
From: Fooxboy
Date: Sat, 27 Apr 2024 10:18:20 +0300
Subject: [PATCH 14/18] #365 Audio dislike
---
MusicX.Core/Services/VkService.cs | 27 +++++++++++++++
MusicX/Controls/PlayerControl.xaml | 17 ++++++++++
MusicX/Controls/PlayerControl.xaml.cs | 48 +++++++++++++++++++++++++++
3 files changed, 92 insertions(+)
diff --git a/MusicX.Core/Services/VkService.cs b/MusicX.Core/Services/VkService.cs
index 66fc81cc..52261962 100644
--- a/MusicX.Core/Services/VkService.cs
+++ b/MusicX.Core/Services/VkService.cs
@@ -1158,5 +1158,32 @@ public async Task GetLyrics(string audioId)
throw;
}
}
+
+ public async Task Dislike(long audioId, long ownerId)
+ {
+ try
+ {
+ logger.Info($"Invoke 'audio.addDislike' with audioId {audioId}");
+ var parameters = new VkParameters
+ {
+
+ {"device_id", await _deviceIdStore.GetDeviceIdAsync()},
+
+ {"audio_ids", $"{ownerId}_{audioId}"},
+ };
+
+ var json = await apiInvoke.InvokeAsync("audio.addDislike", parameters);
+ logger.Debug("RESULT OF 'audio.addDislike'" + json);
+
+ logger.Info("Successful invoke 'audio.addDislike' ");
+
+ }
+ catch (Exception ex)
+ {
+ logger.Error("VK API ERROR:");
+ logger.Error(ex, ex.Message);
+ throw;
+ }
+ }
}
}
diff --git a/MusicX/Controls/PlayerControl.xaml b/MusicX/Controls/PlayerControl.xaml
index 1cf33e81..81c02035 100644
--- a/MusicX/Controls/PlayerControl.xaml
+++ b/MusicX/Controls/PlayerControl.xaml
@@ -330,6 +330,23 @@
+
+
+
+
+
+
+
+
+
(lyricsViewModel);
}
+
+ private async void DislikeButton_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ var vkService = StaticService.Container.GetRequiredService();
+
+ if (PlayerService.CurrentTrack?.Data is VkTrackData vkData)
+ {
+ await vkService.Dislike(vkData.Info.Id, vkData.Info.OwnerId);
+ }
+
+ await PlayerService.NextTrack();
+ }
+ catch(Exception ex)
+ {
+ var properties = new Dictionary
+ {
+ {"Version", StaticService.Version }
+ };
+ Crashes.TrackError(ex, properties);
+
+ logger.Error("Error in dislike track");
+ logger.Error(ex, ex.Message);
+
+ var snackbarService = StaticService.Container.GetRequiredService();
+
+ snackbarService.ShowException("Мы не смогли указать, что Вам этот трек не нравится", ex);
+ }
+ }
}
}
From 9126248251db869daa82869e3bb5749c83d79952 Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Sat, 27 Apr 2024 22:01:54 +0700
Subject: [PATCH 15/18] add lastfm scrobbling
---
.gitmodules | 3 +
MusicX.sln | 6 +
MusicX/Models/ConfigModel.cs | 5 +
MusicX/MusicX.csproj | 1 +
.../Player/TrackStats/DiscordTrackStats.cs | 2 +-
.../Player/TrackStats/ITrackStatsListener.cs | 2 +-
.../Services/Player/TrackStats/LastFmStats.cs | 49 +
.../Player/TrackStats/ListenTogetherStats.cs | 2 +-
.../TrackStats/VkTrackBroadcastStats.cs | 2 +-
.../Player/TrackStats/VkTrackStats.cs | 4 +-
.../Modals/LastFmAuthModalViewModel.cs | 64 +
MusicX/Views/Modals/LastFmAuthModal.xaml | 35 +
MusicX/Views/Modals/LastFmAuthModal.xaml.cs | 19 +
MusicX/Views/SettingsView.xaml | 8 +
MusicX/Views/SettingsView.xaml.cs | 13 +
MusicX/Views/StartingWindow.xaml.cs | 15 +
MusicX/packages.lock.json | 1199 +++++++++++++++++
lastfm | 1 +
18 files changed, 1424 insertions(+), 6 deletions(-)
create mode 100644 .gitmodules
create mode 100644 MusicX/Services/Player/TrackStats/LastFmStats.cs
create mode 100644 MusicX/ViewModels/Modals/LastFmAuthModalViewModel.cs
create mode 100644 MusicX/Views/Modals/LastFmAuthModal.xaml
create mode 100644 MusicX/Views/Modals/LastFmAuthModal.xaml.cs
create mode 160000 lastfm
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..cc3083f0
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "lastfm"]
+ path = lastfm
+ url = https://github.com/tolbxela/lastfm.git
diff --git a/MusicX.sln b/MusicX.sln
index 6a03c4dc..0f9bad8b 100644
--- a/MusicX.sln
+++ b/MusicX.sln
@@ -28,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMediaToolkit", "FFMediaTo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PsCompiler", "PsCompiler\PsCompiler.csproj", "{449B9F89-C7C2-4C7E-9F2F-28CBB731E7F5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IF.Lastfm.Core", "lastfm\src\IF.Lastfm.Core\IF.Lastfm.Core.csproj", "{57464921-52F3-4172-83E9-0B7D75F9DDDF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -74,6 +76,10 @@ Global
{449B9F89-C7C2-4C7E-9F2F-28CBB731E7F5}.Debug|x64.Build.0 = Debug|Any CPU
{449B9F89-C7C2-4C7E-9F2F-28CBB731E7F5}.Release|x64.ActiveCfg = Release|Any CPU
{449B9F89-C7C2-4C7E-9F2F-28CBB731E7F5}.Release|x64.Build.0 = Release|Any CPU
+ {57464921-52F3-4172-83E9-0B7D75F9DDDF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {57464921-52F3-4172-83E9-0B7D75F9DDDF}.Debug|x64.Build.0 = Debug|Any CPU
+ {57464921-52F3-4172-83E9-0B7D75F9DDDF}.Release|x64.ActiveCfg = Release|Any CPU
+ {57464921-52F3-4172-83E9-0B7D75F9DDDF}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/MusicX/Models/ConfigModel.cs b/MusicX/Models/ConfigModel.cs
index 5b83a412..b96b4fb7 100644
--- a/MusicX/Models/ConfigModel.cs
+++ b/MusicX/Models/ConfigModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using IF.Lastfm.Core.Objects;
using MusicX.Services.Player;
namespace MusicX.Models
@@ -58,5 +59,9 @@ public class ConfigModel
public bool? SavePlayerState { get; set; }
public PlayerState? LastPlayerState { get; set; }
+
+ public LastUserSession? LastFmSession { get; set; }
+
+ public bool? SendLastFmScrobbles { get; set; }
}
}
diff --git a/MusicX/MusicX.csproj b/MusicX/MusicX.csproj
index 3d6a016b..b1d061c2 100644
--- a/MusicX/MusicX.csproj
+++ b/MusicX/MusicX.csproj
@@ -109,6 +109,7 @@
+
diff --git a/MusicX/Services/Player/TrackStats/DiscordTrackStats.cs b/MusicX/Services/Player/TrackStats/DiscordTrackStats.cs
index 0fd4ada3..1d56b07d 100644
--- a/MusicX/Services/Player/TrackStats/DiscordTrackStats.cs
+++ b/MusicX/Services/Player/TrackStats/DiscordTrackStats.cs
@@ -18,7 +18,7 @@ public DiscordTrackStats(DiscordService discordService, ConfigService configServ
_configService = configService;
}
- public Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason)
+ public Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason, TimeSpan? position = null)
{
if (_configService.Config.ShowRPC == true)
SetTrack(newTrack);
diff --git a/MusicX/Services/Player/TrackStats/ITrackStatsListener.cs b/MusicX/Services/Player/TrackStats/ITrackStatsListener.cs
index f66711a0..8a470d06 100644
--- a/MusicX/Services/Player/TrackStats/ITrackStatsListener.cs
+++ b/MusicX/Services/Player/TrackStats/ITrackStatsListener.cs
@@ -7,6 +7,6 @@ namespace MusicX.Services.Player.TrackStats;
public interface ITrackStatsListener
{
- Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason);
+ Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason, TimeSpan? position = null);
Task TrackPlayStateChangedAsync(PlaylistTrack track, TimeSpan position, bool paused);
}
\ No newline at end of file
diff --git a/MusicX/Services/Player/TrackStats/LastFmStats.cs b/MusicX/Services/Player/TrackStats/LastFmStats.cs
new file mode 100644
index 00000000..c127f74a
--- /dev/null
+++ b/MusicX/Services/Player/TrackStats/LastFmStats.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api;
+using IF.Lastfm.Core.Objects;
+using IF.Lastfm.Core.Scrobblers;
+using MusicX.Models.Enums;
+using MusicX.Shared.Player;
+
+namespace MusicX.Services.Player.TrackStats;
+
+public class LastFmStats : ITrackStatsListener
+{
+ private readonly IScrobbler _scrobbler;
+ private readonly ITrackApi _trackApi;
+ private readonly ConfigService _configService;
+
+ public LastFmStats(IScrobbler scrobbler, ITrackApi trackApi, ConfigService configService)
+ {
+ _scrobbler = scrobbler;
+ _trackApi = trackApi;
+ _configService = configService;
+ }
+
+ public async Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason, TimeSpan? position = null)
+ {
+ if (_configService.Config.LastFmSession is null || _configService.Config.SendLastFmScrobbles is not true)
+ return;
+
+ if (previousTrack is not null && previousTrack.Data.Duration > TimeSpan.FromSeconds(30) &&
+ position.HasValue && (position.Value > TimeSpan.FromMinutes(4) || position.Value > previousTrack.Data.Duration / 2))
+ await _scrobbler.ScrobbleAsync(new Scrobble(previousTrack.MainArtists.First().Name,
+ previousTrack.AlbumId?.Name, previousTrack.Title, DateTimeOffset.Now - position.Value)
+ {
+ Duration = previousTrack.Data.Duration
+ });
+
+ await _trackApi.UpdateNowPlayingAsync(new Scrobble(newTrack.MainArtists.First().Name, newTrack.AlbumId?.Name,
+ newTrack.Title, DateTimeOffset.Now)
+ {
+ Duration = newTrack.Data.Duration
+ });
+ }
+
+ public Task TrackPlayStateChangedAsync(PlaylistTrack track, TimeSpan position, bool paused)
+ {
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/MusicX/Services/Player/TrackStats/ListenTogetherStats.cs b/MusicX/Services/Player/TrackStats/ListenTogetherStats.cs
index 7caa5ee1..e7955c09 100644
--- a/MusicX/Services/Player/TrackStats/ListenTogetherStats.cs
+++ b/MusicX/Services/Player/TrackStats/ListenTogetherStats.cs
@@ -15,7 +15,7 @@ public ListenTogetherStats(ListenTogetherService listenTogetherService)
this._listenTogetherService = listenTogetherService;
}
- public async Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason)
+ public async Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason, TimeSpan? position = null)
{
if (_listenTogetherService.PlayerMode != PlayerMode.Owner) return;
diff --git a/MusicX/Services/Player/TrackStats/VkTrackBroadcastStats.cs b/MusicX/Services/Player/TrackStats/VkTrackBroadcastStats.cs
index cacfaf44..a4230066 100644
--- a/MusicX/Services/Player/TrackStats/VkTrackBroadcastStats.cs
+++ b/MusicX/Services/Player/TrackStats/VkTrackBroadcastStats.cs
@@ -27,7 +27,7 @@ public VkTrackBroadcastStats(VkService vkService, ConfigService configService, L
_snackbarService = snackbarService;
}
- public Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason)
+ public Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason, TimeSpan? position = null)
{
try
{
diff --git a/MusicX/Services/Player/TrackStats/VkTrackStats.cs b/MusicX/Services/Player/TrackStats/VkTrackStats.cs
index 531e9310..05bc155e 100644
--- a/MusicX/Services/Player/TrackStats/VkTrackStats.cs
+++ b/MusicX/Services/Player/TrackStats/VkTrackStats.cs
@@ -17,7 +17,7 @@ public VkTrackStats(VkService vkService)
_vkService = vkService;
}
- public async Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason)
+ public async Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack newTrack, ChangeReason reason, TimeSpan? position = null)
{
if (newTrack.Data is not VkTrackData newTrackData)
return;
@@ -55,7 +55,7 @@ public async Task TrackChangedAsync(PlaylistTrack? previousTrack, PlaylistTrack
PlaybackStartedAt = "0",
TrackCode = previousTrackData.TrackCode,
StreamingType = "online",
- Duration = previousTrackData.Duration.TotalSeconds.ToString(),
+ Duration = (position ?? previousTrackData.Duration).TotalSeconds.ToString(),
Repeat = "all",
State = "app",
Source = newTrackData.ParentBlockId!,
diff --git a/MusicX/ViewModels/Modals/LastFmAuthModalViewModel.cs b/MusicX/ViewModels/Modals/LastFmAuthModalViewModel.cs
new file mode 100644
index 00000000..3eab33a4
--- /dev/null
+++ b/MusicX/ViewModels/Modals/LastFmAuthModalViewModel.cs
@@ -0,0 +1,64 @@
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using AsyncAwaitBestPractices.MVVM;
+using IF.Lastfm.Core.Api;
+using IF.Lastfm.Core.Api.Helpers;
+using MusicX.Services;
+using Wpf.Ui;
+using Wpf.Ui.Controls;
+using Wpf.Ui.Extensions;
+using NavigationService = MusicX.Services.NavigationService;
+
+namespace MusicX.ViewModels.Modals;
+
+public class LastFmAuthModalViewModel : BaseViewModel
+{
+ private readonly ILastAuth _auth;
+ private readonly NavigationService _navigationService;
+ private readonly ISnackbarService _snackbarService;
+ private readonly ConfigService _configService;
+ private string? _token;
+
+ public ICommand ConfirmCommand { get; }
+
+ public LastFmAuthModalViewModel(ILastAuth auth, NavigationService navigationService, ISnackbarService snackbarService, ConfigService configService)
+ {
+ _auth = auth;
+ _navigationService = navigationService;
+ _snackbarService = snackbarService;
+ _configService = configService;
+
+ ConfirmCommand = new AsyncCommand(ConfirmAsync);
+ }
+
+ private async Task ConfirmAsync()
+ {
+ if (_token is null)
+ return;
+
+ var response = await _auth.GetSessionTokenAsync(_token);
+
+ if (!response.Success)
+ {
+ _snackbarService.Show("Ошибка авторизации", $"Сервис вернул: {response.Status}", ControlAppearance.Danger);
+ return;
+ }
+
+ _navigationService.CloseModal();
+
+ _configService.Config.LastFmSession = _auth.UserSession;
+ await _configService.SetConfig(_configService.Config);
+ }
+
+ public async Task OpenAuthPageAsync()
+ {
+ _token = ((LastResponse)await _auth.GetAuthTokenAsync()).Content;
+
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = $"https://last.fm/api/auth/?api_key={_auth.ApiKey}&token={_token}",
+ UseShellExecute = true
+ });
+ }
+}
\ No newline at end of file
diff --git a/MusicX/Views/Modals/LastFmAuthModal.xaml b/MusicX/Views/Modals/LastFmAuthModal.xaml
new file mode 100644
index 00000000..053742b7
--- /dev/null
+++ b/MusicX/Views/Modals/LastFmAuthModal.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+ Подтвердите вход в аккаунт в браузере
+
+
+
+
+
+
+
+ Готово
+
+
+
diff --git a/MusicX/Views/Modals/LastFmAuthModal.xaml.cs b/MusicX/Views/Modals/LastFmAuthModal.xaml.cs
new file mode 100644
index 00000000..1244a9f4
--- /dev/null
+++ b/MusicX/Views/Modals/LastFmAuthModal.xaml.cs
@@ -0,0 +1,19 @@
+using System.Windows;
+using System.Windows.Controls;
+using MusicX.ViewModels.Modals;
+
+namespace MusicX.Views.Modals;
+
+public partial class LastFmAuthModal : Page
+{
+ public LastFmAuthModal()
+ {
+ InitializeComponent();
+ }
+
+ private async void LastFmAuthModal_OnLoaded(object sender, RoutedEventArgs e)
+ {
+ if (DataContext is LastFmAuthModalViewModel viewModel)
+ await viewModel.OpenAuthPageAsync();
+ }
+}
\ No newline at end of file
diff --git a/MusicX/Views/SettingsView.xaml b/MusicX/Views/SettingsView.xaml
index 72d0fdd2..c8946307 100644
--- a/MusicX/Views/SettingsView.xaml
+++ b/MusicX/Views/SettingsView.xaml
@@ -104,6 +104,14 @@
Content="Показывать трек в статусе ВКонтакте"
FontSize="15"
Unchecked="BroacastVK_Unchecked" />
+
+
diff --git a/MusicX/Views/SettingsView.xaml.cs b/MusicX/Views/SettingsView.xaml.cs
index 326e2f8b..fff664f9 100644
--- a/MusicX/Views/SettingsView.xaml.cs
+++ b/MusicX/Views/SettingsView.xaml.cs
@@ -14,6 +14,7 @@
using MusicX.Models;
using MusicX.Services;
using MusicX.ViewModels;
+using MusicX.ViewModels.Modals;
using MusicX.Views.Login;
using MusicX.Views.Modals;
using NLog;
@@ -72,6 +73,7 @@ private async void SettingsView_Loaded(object sender, RoutedEventArgs e)
MinimizeToTray.IsChecked = config.MinimizeToTray.GetValueOrDefault();
GetBetaUpdates.IsChecked = config.GetBetaUpdates.GetValueOrDefault();
SavePlayerState.IsChecked = config.SavePlayerState.GetValueOrDefault();
+ SendLastFm.IsChecked = config.SendLastFmScrobbles.GetValueOrDefault();
UserName.Text = config.UserName;
@@ -571,5 +573,16 @@ private async void SavePlayerState_OnCheckChanged(object sender, RoutedEventArgs
await configService.SetConfig(config);
}
+
+ private async void SendLastFm_OnChanged(object sender, RoutedEventArgs e)
+ {
+ config.SendLastFmScrobbles = SendLastFm.IsChecked;
+
+ await configService.SetConfig(config);
+
+ if (config.LastFmSession is null)
+ StaticService.Container.GetRequiredService()
+ .OpenModal(StaticService.Container.GetRequiredService());
+ }
}
}
diff --git a/MusicX/Views/StartingWindow.xaml.cs b/MusicX/Views/StartingWindow.xaml.cs
index 41aa0f8d..1ff74b88 100644
--- a/MusicX/Views/StartingWindow.xaml.cs
+++ b/MusicX/Views/StartingWindow.xaml.cs
@@ -5,6 +5,8 @@
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
+using IF.Lastfm.Core.Api;
+using IF.Lastfm.Core.Scrobblers;
using Microsoft.AppCenter.Analytics;
using Microsoft.Extensions.DependencyInjection;
using MusicX.Core.Services;
@@ -87,6 +89,7 @@ await Task.Run(async () =>
collection.AddSingleton();
collection.AddSingleton();
collection.AddSingleton();
+ collection.AddSingleton();
collection.AddTransient();
collection.AddTransient();
@@ -102,6 +105,7 @@ await Task.Run(async () =>
collection.AddTransient();
collection.AddTransient();
collection.AddTransient();
+ collection.AddTransient();
collection.AddSingleton();
collection.AddSingleton();
@@ -113,6 +117,17 @@ await Task.Run(async () =>
collection.AddSingleton();
collection.AddSingleton();
collection.AddSingleton();
+ collection.AddSingleton(s =>
+ {
+ var auth = new LastAuth("ff3b1adf454799a760ad29a0b71bd6b3", "74ef981214417381716f72a46677a802");
+
+ if (s.GetRequiredService().Config.LastFmSession is { } session)
+ auth.LoadSession(session);
+
+ return auth;
+ });
+ collection.AddSingleton();
+ collection.AddSingleton();
var container = StaticService.Container = collection.BuildServiceProvider();
diff --git a/MusicX/packages.lock.json b/MusicX/packages.lock.json
index 7324859d..0037fdc6 100644
--- a/MusicX/packages.lock.json
+++ b/MusicX/packages.lock.json
@@ -381,6 +381,16 @@
"resolved": "8.0.0",
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
},
+ "Microsoft.NETCore.Platforms": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
+ },
+ "Microsoft.NETCore.Targets": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
+ },
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "4.7.0",
@@ -459,6 +469,104 @@
"System.Drawing.Common": "6.0.0"
}
},
+ "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q=="
+ },
+ "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA=="
+ },
+ "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw=="
+ },
+ "runtime.native.System": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "runtime.native.System.Net.Http": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "runtime.native.System.Security.Cryptography.Apple": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==",
+ "dependencies": {
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0"
+ }
+ },
+ "runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==",
+ "dependencies": {
+ "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A=="
+ },
+ "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ=="
+ },
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ=="
+ },
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g=="
+ },
+ "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg=="
+ },
+ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ=="
+ },
+ "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A=="
+ },
+ "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg=="
+ },
"SQLitePCLRaw.bundle_green": {
"type": "Transitive",
"resolved": "2.1.5",
@@ -494,6 +602,33 @@
"resolved": "6.0.0",
"contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
},
+ "System.Collections": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Collections.Concurrent": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -503,6 +638,38 @@
"System.Security.Permissions": "6.0.0"
}
},
+ "System.Diagnostics.Debug": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Diagnostics.DiagnosticSource": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Diagnostics.Tracing": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
"System.Drawing.Common": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -511,11 +678,92 @@
"Microsoft.Win32.SystemEvents": "8.0.0"
}
},
+ "System.Globalization": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Globalization.Calendars": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Globalization.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0"
+ }
+ },
+ "System.IO": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.IO.FileSystem": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.IO.FileSystem.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==",
+ "dependencies": {
+ "System.Runtime": "4.3.0"
+ }
+ },
"System.IO.Pipelines": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA=="
},
+ "System.Linq": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0"
+ }
+ },
"System.Management": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -529,26 +777,301 @@
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
+ "System.Net.Http": {
+ "type": "Transitive",
+ "resolved": "4.3.2",
+ "contentHash": "y7hv0o0weI0j0mvEcBOdt1F3CAADiWlcw3e54m8TfYiRmBPDIsHElx8QUPDlY4x6yWXKPGN0Z2TuXCTPgkm5WQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.DiagnosticSource": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Extensions": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.Net.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.OpenSsl": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Security.Cryptography.X509Certificates": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.Net.Http": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Net.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0"
+ }
+ },
+ "System.Reflection": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
"System.Reflection.Emit": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ=="
},
+ "System.Reflection.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Resources.ResourceManager": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
+ "System.Runtime.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime.Handles": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime.InteropServices": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0"
+ }
+ },
+ "System.Runtime.Numerics": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==",
+ "dependencies": {
+ "System.Globalization": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0"
+ }
+ },
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ=="
},
+ "System.Security.Cryptography.Algorithms": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.Apple": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Cng": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Csp": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Collections.Concurrent": "4.3.0",
+ "System.Linq": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==",
+ "dependencies": {
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ=="
},
+ "System.Security.Cryptography.X509Certificates": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Calendars": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Cng": "4.3.0",
+ "System.Security.Cryptography.Csp": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.OpenSsl": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.Net.Http": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
"System.Security.Permissions": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -563,16 +1086,45 @@
"resolved": "4.7.0",
"contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ=="
},
+ "System.Text.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
},
+ "System.Threading": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
+ "dependencies": {
+ "System.Runtime": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
"System.Threading.Channels": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA=="
},
+ "System.Threading.Tasks": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
"System.ValueTuple": {
"type": "Transitive",
"resolved": "4.5.0",
@@ -611,6 +1163,13 @@
"FFmpeg.AutoGen": "[5.1.2.3, )"
}
},
+ "if.lastfm.core": {
+ "type": "Project",
+ "dependencies": {
+ "Newtonsoft.Json": "[9.0.1, )",
+ "System.Net.Http": "[4.3.2, )"
+ }
+ },
"musicx.core": {
"type": "Project",
"dependencies": {
@@ -686,6 +1245,17 @@
"Microsoft.Windows.CsWinRT": "2.0.0"
}
},
+ "Microsoft.Win32.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.win.Microsoft.Win32.Primitives": "4.3.0"
+ }
+ },
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "4.7.0",
@@ -700,11 +1270,318 @@
"resolved": "8.0.0",
"contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw=="
},
+ "runtime.any.System.Collections": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "23g6rqftKmovn2cLeGsuHUYm0FD7pdutb0uQMJpZ3qTvq+zHkgmt6J65VtRry4WDGYlmkMa4xDACtaQ94alNag==",
+ "dependencies": {
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "runtime.any.System.Diagnostics.Tracing": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "1lpifymjGDzoYIaam6/Hyqf8GhBI3xXYLK2TgEvTtuZMorG3Kb9QnMTIKhLjJYXIiu1JvxjngHvtVFQQlpQ3HQ=="
+ },
+ "runtime.any.System.Globalization": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "sMDBnad4rp4t7GY442Jux0MCUuKL4otn5BK6Ni0ARTXTSpRNBzZ7hpMfKSvnVSED5kYJm96YOWsqV0JH0d2uuw=="
+ },
+ "runtime.any.System.Globalization.Calendars": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "M1r+760j1CNA6M/ZaW6KX8gOS8nxPRqloqDcJYVidRG566Ykwcs29AweZs2JF+nMOCgWDiMfPSTMfvwOI9F77w=="
+ },
+ "runtime.any.System.IO": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ=="
+ },
+ "runtime.any.System.Reflection": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ=="
+ },
+ "runtime.any.System.Reflection.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg=="
+ },
+ "runtime.any.System.Resources.ResourceManager": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "Lxb89SMvf8w9p9+keBLyL6H6x/TEmc6QVsIIA0T36IuyOY3kNvIdyGddA2qt35cRamzxF8K5p0Opq4G4HjNbhQ=="
+ },
+ "runtime.any.System.Runtime": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==",
+ "dependencies": {
+ "System.Private.Uri": "4.3.0"
+ }
+ },
+ "runtime.any.System.Runtime.Handles": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "GG84X6vufoEzqx8PbeBKheE4srOhimv+yLtGb/JkR3Y2FmoqmueLNFU4Xx8Y67plFpltQSdK74x0qlEhIpv/CQ=="
+ },
+ "runtime.any.System.Runtime.InteropServices": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "lBoFeQfxe/4eqjPi46E0LU/YaCMdNkQ8B4MZu/mkzdIAZh8RQ1NYZSj0egrQKdgdvlPFtP4STtob40r4o2DBAw=="
+ },
+ "runtime.any.System.Text.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ=="
+ },
+ "runtime.any.System.Text.Encoding.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "NLrxmLsfRrOuVqPWG+2lrQZnE53MLVeo+w9c54EV+TUo4c8rILpsDXfY8pPiOy9kHpUHHP07ugKmtsU3vVW5Jg=="
+ },
+ "runtime.any.System.Threading.Tasks": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w=="
+ },
+ "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q=="
+ },
+ "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA=="
+ },
+ "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw=="
+ },
+ "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A=="
+ },
+ "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ=="
+ },
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ=="
+ },
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g=="
+ },
+ "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg=="
+ },
+ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ=="
+ },
+ "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A=="
+ },
+ "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg=="
+ },
+ "runtime.win.Microsoft.Win32.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "NU51SEt/ZaD2MF48sJ17BIqx7rjeNNLXUevfMOjqQIetdndXwYjZfZsT6jD+rSWp/FYxjesdK4xUSl4OTEI0jw==",
+ "dependencies": {
+ "System.Runtime": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0"
+ }
+ },
+ "runtime.win.System.Diagnostics.Debug": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "hHHP0WCStene2jjeYcuDkETozUYF/3sHVRHAEOgS3L15hlip24ssqCTnJC28Z03Wpo078oMcJd0H4egD2aJI8g=="
+ },
+ "runtime.win.System.IO.FileSystem": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "Z37zcSCpXuGCYtFbqYO0TwOVXxS2d+BXgSoDFZmRg8BC4Cuy54edjyIvhhcfCrDQA9nl+EPFTgHN54dRAK7mNA==",
+ "dependencies": {
+ "System.Buffers": "4.3.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Text.Encoding.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Overlapped": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "runtime.win.System.Net.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "lkXXykakvXUU+Zq2j0pC6EO20lEhijjqMc01XXpp1CJN+DeCwl3nsj4t5Xbpz3kA7yQyTqw6d9SyIzsyLsV3zA==",
+ "dependencies": {
+ "Microsoft.Win32.Primitives": "4.3.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "runtime.win.System.Runtime.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "RkgHVhUPvzZxuUubiZe8yr/6CypRVXj0VBzaR8hsqQ8f+rUo7e4PWrHTLOCjd8fBMGWCrY//fi7Ku3qXD7oHRw==",
+ "dependencies": {
+ "System.Private.Uri": "4.3.0"
+ }
+ },
+ "runtime.win7.System.Private.Uri": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "Q+IBgaPYicSQs2tBlmXqbS25c/JLIthWrgrpMwxKSOobW/OqIMVFruUGfuaz4QABVzV8iKdCAbN7APY7Tclbnw=="
+ },
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.5",
"contentHash": "Fqp/FQlb+USnEC2qfWOdsY4fFir3sob9BQMgdT3rcamUAoB7id8V0WknWdsFnE4TXBKDiM79+oPoZoHAuU/dsg=="
},
+ "System.Buffers": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==",
+ "dependencies": {
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Collections": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Collections": "4.3.0"
+ }
+ },
+ "System.Diagnostics.Debug": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.win.System.Diagnostics.Debug": "4.3.0"
+ }
+ },
+ "System.Diagnostics.Tracing": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Diagnostics.Tracing": "4.3.0"
+ }
+ },
+ "System.Globalization": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Globalization": "4.3.0"
+ }
+ },
+ "System.Globalization.Calendars": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Globalization.Calendars": "4.3.0"
+ }
+ },
+ "System.Globalization.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0"
+ }
+ },
+ "System.IO": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "runtime.any.System.IO": "4.3.0"
+ }
+ },
+ "System.IO.FileSystem": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "runtime.win.System.IO.FileSystem": "4.3.0"
+ }
+ },
"System.Management": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -713,26 +1590,348 @@
"System.CodeDom": "6.0.0"
}
},
+ "System.Net.Http": {
+ "type": "Transitive",
+ "resolved": "4.3.2",
+ "contentHash": "y7hv0o0weI0j0mvEcBOdt1F3CAADiWlcw3e54m8TfYiRmBPDIsHElx8QUPDlY4x6yWXKPGN0Z2TuXCTPgkm5WQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.DiagnosticSource": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Extensions": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.Net.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.OpenSsl": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Security.Cryptography.X509Certificates": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.Net.Http": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Net.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "runtime.win.System.Net.Primitives": "4.3.0"
+ }
+ },
+ "System.Private.Uri": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "runtime.win7.System.Private.Uri": "4.3.0"
+ }
+ },
+ "System.Reflection": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Reflection": "4.3.0"
+ }
+ },
+ "System.Reflection.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Reflection.Primitives": "4.3.0"
+ }
+ },
+ "System.Resources.ResourceManager": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Resources.ResourceManager": "4.3.0"
+ }
+ },
+ "System.Runtime": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "runtime.any.System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.win.System.Runtime.Extensions": "4.3.0"
+ }
+ },
+ "System.Runtime.Handles": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Runtime.Handles": "4.3.0"
+ }
+ },
+ "System.Runtime.InteropServices": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "runtime.any.System.Runtime.InteropServices": "4.3.0"
+ }
+ },
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ=="
},
+ "System.Security.Cryptography.Algorithms": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.Apple": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Cng": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Csp": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Collections.Concurrent": "4.3.0",
+ "System.Linq": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ=="
},
+ "System.Security.Cryptography.X509Certificates": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Calendars": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Cng": "4.3.0",
+ "System.Security.Cryptography.Csp": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.OpenSsl": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.Net.Http": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
"System.Security.Principal.Windows": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ=="
},
+ "System.Text.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Text.Encoding.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.any.System.Text.Encoding.Extensions": "4.3.0"
+ }
+ },
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
},
+ "System.Threading": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
+ "dependencies": {
+ "System.Runtime": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.Threading.Overlapped": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "m3HQ2dPiX/DSTpf+yJt8B0c+SRvzfqAJKx+QDWi+VLhz8svLT23MVjEOHPF/KiSLeArKU/iHescrbLd3yVgyNg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0"
+ }
+ },
+ "System.Threading.Tasks": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "runtime.any.System.Threading.Tasks": "4.3.0"
+ }
+ },
"System.Windows.Extensions": {
"type": "Transitive",
"resolved": "6.0.0",
diff --git a/lastfm b/lastfm
new file mode 160000
index 00000000..677deaf6
--- /dev/null
+++ b/lastfm
@@ -0,0 +1 @@
+Subproject commit 677deaf6f4a1fc77aeef691eda068a05d039eb0b
From 9e1c77fabce3b338e8b00db155980dd0b651f4e8 Mon Sep 17 00:00:00 2001
From: Fooxboy
Date: Sat, 27 Apr 2024 19:29:22 +0300
Subject: [PATCH 16/18] feature: next version [skip ci]
---
notes.md | 21 +++++++--------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/notes.md b/notes.md
index 48c32419..d1a8c50a 100644
--- a/notes.md
+++ b/notes.md
@@ -1,14 +1,7 @@
-+ Исправили переключение трека при перемешивании
-+ Исправили невозможность отключить перемешивание
-+ Починили игнорирование исполнителей
-+ Повтор одного трека снова работает
-+ Добавили иконки ко всем пунктам контекстного меню трека
-+ Исправили наложение иконок explicit lyrics в треках
-+ Починили редкий краш загрузки страниц некоторых исполнителей
-+ Переделали анимацию загрузки страницы плейлиста
-+ Добавили новые разделы рекомендаций на страницу Каталогов
-+ Исправили работу кнопок в разделе Музыка
-+ Добавили сохранение текущего трека при перезапуске для обновления
-+ Обновили версию вконтакте (Добавились плейлисты по настроению)
-+ Исправили поведение блока ссылок при переходах назад
-+ Провели общую оптимизацию загрузки страниц
++ 💣 Исправлена утечка памяти
++ 🧨 Исправлено сворачивание приложения в трей
++ 🔥 Редизайн заголовка блоков
++ ❤️🔥 В настройки добавлена возможность ручной проверки обновлений
++ ⚡ Редизайн плейлистов "Какой сейчас вайб"
++ 🔫 Добавлена возможность сохранения очереди воспроизведения при закрытии приложения
++ ♨️ Реализована возможность дизлайкать треки, которые не нравятся
From d55a1afc815e46a6c28d5e9239c228d3f3ab911c Mon Sep 17 00:00:00 2001
From: zznty <94796179+zznty@users.noreply.github.com>
Date: Sun, 28 Apr 2024 18:36:21 +0700
Subject: [PATCH 17/18] fix: build and lastfm scrobbling
---
.gitmodules | 3 -
IF.Lastfm.Core/Api/AlbumApi.cs | 102 ++++
IF.Lastfm.Core/Api/ArtistApi.cs | 177 ++++++
IF.Lastfm.Core/Api/ChartApi.cs | 70 +++
.../Api/Commands/Album/AddShoutCommand.cs | 36 ++
.../Api/Commands/Album/GetInfoCommand.cs | 74 +++
.../Api/Commands/Album/GetShoutsCommand.cs | 57 ++
.../Commands/Album/GetTagsByUserCommand.cs | 61 ++
.../Api/Commands/Album/GetTopTagsCommand.cs | 69 +++
.../Api/Commands/Album/SearchCommand.cs | 49 ++
.../Api/Commands/Artist/AddShoutCommand.cs | 31 +
.../Api/Commands/Artist/GetInfoCommand.cs | 67 +++
.../Api/Commands/Artist/GetShoutsCommand.cs | 53 ++
.../Api/Commands/Artist/GetSimilarCommand.cs | 67 +++
.../Commands/Artist/GetTagsByUserCommand.cs | 57 ++
.../Commands/Artist/GetTopAlbumsCommand.cs | 54 ++
.../Api/Commands/Artist/GetTopTagsCommand.cs | 71 +++
.../Commands/Artist/GetTopTracksCommand.cs | 50 ++
.../Api/Commands/Artist/SearchCommand.cs | 49 ++
.../Commands/Auth/GetMobileSessionCommand.cs | 54 ++
.../Api/Commands/Auth/GetSessionCommand.cs | 49 ++
.../Api/Commands/Auth/GetTokenCommand.cs | 42 ++
.../Commands/Chart/GetTopArtistsCommand.cs | 41 ++
.../Api/Commands/Chart/GetTopTagsCommand.cs | 43 ++
.../Api/Commands/Chart/GetTopTracksCommand.cs | 42 ++
.../Api/Commands/GetAsyncCommandBase.cs | 53 ++
IF.Lastfm.Core/Api/Commands/IAsyncCommand.cs | 10 +
.../Api/Commands/LastAsyncCommandBase.cs | 93 +++
.../Api/Commands/Library/GetArtistsCommand.cs | 49 ++
.../Api/Commands/Library/GetTracksCommand.cs | 63 +++
.../Commands/Library/RemoveScrobbleCommand.cs | 31 +
.../Commands/Library/RemoveTrackCommand.cs | 27 +
.../Api/Commands/PostAsyncCommandBase.cs | 65 +++
.../Api/Commands/Tag/GetInfoCommand.cs | 47 ++
.../Api/Commands/Tag/GetSimilarCommand.cs | 47 ++
.../Api/Commands/Tag/GetTopAlbumsCommand.cs | 47 ++
.../Api/Commands/Tag/GetTopArtistsCommand.cs | 53 ++
.../Api/Commands/Tag/GetTopTagsCommand.cs | 40 ++
.../Api/Commands/Tag/GetTopTracksCommand.cs | 47 ++
.../Api/Commands/Track/AddShoutCommand.cs | 36 ++
.../Api/Commands/Track/GetInfoCommand.cs | 65 +++
.../Api/Commands/Track/GetShoutsCommand.cs | 57 ++
.../Api/Commands/Track/GetSimilarCommand.cs | 63 +++
.../Api/Commands/Track/LoveCommand.cs | 32 ++
.../Api/Commands/Track/ScrobbleCommand.cs | 68 +++
.../Api/Commands/Track/SearchCommand.cs | 54 ++
.../Api/Commands/Track/UnloveCommand.cs | 32 ++
.../Commands/Track/UpdateNowPlayingCommand.cs | 58 ++
.../UnauthenticatedPostAsyncCommandBase.cs | 17 +
.../Api/Commands/User/AddShoutCommand.cs | 32 ++
.../Api/Commands/User/GetInfoCommand.cs | 47 ++
.../Commands/User/GetLovedTracksCommand.cs | 52 ++
.../Commands/User/GetRecentStationsCommand.cs | 55 ++
.../Commands/User/GetRecentTracksCommand.cs | 70 +++
.../User/GetRecommendedArtistsCommand.cs | 40 ++
.../Api/Commands/User/GetShoutsCommand.cs | 49 ++
.../Api/Commands/User/GetTopAlbumsCommand.cs | 51 ++
.../Api/Commands/User/GetTopArtistsCommand.cs | 51 ++
.../User/GetWeeklyAlbumChartCommand.cs | 56 ++
.../User/GetWeeklyArtistChartCommand.cs | 56 ++
.../User/GetWeeklyChartListCommand.cs | 46 ++
.../User/GetWeeklyTrackChartCommand.cs | 56 ++
.../Api/Enums/LastResponseStatus.cs | 99 ++++
IF.Lastfm.Core/Api/Enums/LastStatsTimeSpan.cs | 39 ++
IF.Lastfm.Core/Api/Helpers/ApiExtensions.cs | 54 ++
.../Api/Helpers/ApiNameAttribute.cs | 25 +
IF.Lastfm.Core/Api/Helpers/LastResponse.cs | 62 ++
IF.Lastfm.Core/Api/Helpers/LastResponse{T}.cs | 20 +
IF.Lastfm.Core/Api/Helpers/PageResponse.cs | 227 ++++++++
IF.Lastfm.Core/Api/IAlbumApi.cs | 41 ++
IF.Lastfm.Core/Api/IArtistApi.cs | 57 ++
IF.Lastfm.Core/Api/IChartApi.cs | 19 +
IF.Lastfm.Core/Api/ILastAuth.cs | 55 ++
IF.Lastfm.Core/Api/ILibraryAPI.cs | 35 ++
IF.Lastfm.Core/Api/ITagApi.cs | 16 +
IF.Lastfm.Core/Api/ITrackApi.cs | 39 ++
IF.Lastfm.Core/Api/IUserApi.cs | 53 ++
IF.Lastfm.Core/Api/LastAuth.cs | 117 ++++
IF.Lastfm.Core/Api/LastfmClient.cs | 59 ++
IF.Lastfm.Core/Api/LibraryApi.cs | 54 ++
IF.Lastfm.Core/Api/TagApi.cs | 103 ++++
IF.Lastfm.Core/Api/TrackApi.cs | 118 ++++
IF.Lastfm.Core/Api/UserApi.cs | 184 ++++++
IF.Lastfm.Core/Helpers/ApiBase.cs | 41 ++
.../Helpers/CountingHttpClientHandler.cs | 17 +
.../Helpers/EnumerableExtensions.cs | 29 +
IF.Lastfm.Core/Helpers/FakeResponseHandler.cs | 36 ++
.../Helpers/QueueFakeResponseHandler.cs | 36 ++
IF.Lastfm.Core/IF.Lastfm.Core.csproj | 18 +
IF.Lastfm.Core/Json/LastFmBooleanConverter.cs | 25 +
.../Json/PageResponseJsonConverter.cs | 39 ++
IF.Lastfm.Core/LastFm.cs | 126 +++++
IF.Lastfm.Core/MD5.cs | 295 ++++++++++
IF.Lastfm.Core/Objects/BuyLink.cs | 6 +
IF.Lastfm.Core/Objects/CountryCode.cs | 6 +
IF.Lastfm.Core/Objects/ILastFmObject.cs | 7 +
IF.Lastfm.Core/Objects/LastAlbum.cs | 142 +++++
IF.Lastfm.Core/Objects/LastArtist.cs | 156 +++++
IF.Lastfm.Core/Objects/LastImageSet.cs | 91 +++
IF.Lastfm.Core/Objects/LastShout.cs | 46 ++
IF.Lastfm.Core/Objects/LastStation.cs | 21 +
IF.Lastfm.Core/Objects/LastStats.cs | 25 +
IF.Lastfm.Core/Objects/LastTag.cs | 77 +++
IF.Lastfm.Core/Objects/LastTrack.cs | 192 +++++++
IF.Lastfm.Core/Objects/LastUser.cs | 74 +++
IF.Lastfm.Core/Objects/LastUserSession.cs | 17 +
IF.Lastfm.Core/Objects/LastWeeklyChartList.cs | 38 ++
IF.Lastfm.Core/Objects/LastWiki.cs | 52 ++
IF.Lastfm.Core/Objects/Scrobble.cs | 93 +++
IF.Lastfm.Core/Scrobblers/IScrobbler.cs | 17 +
IF.Lastfm.Core/Scrobblers/MemoryScrobbler.cs | 49 ++
IF.Lastfm.Core/Scrobblers/ScrobbleResponse.cs | 63 +++
IF.Lastfm.Core/Scrobblers/ScrobblerBase.cs | 114 ++++
IF.Lastfm.Core/packages.lock.json | 420 ++++++++++++++
MusicX.sln | 2 +-
MusicX/MusicX.csproj | 2 +-
MusicX/Services/Player/PlayerService.cs | 16 +-
MusicX/packages.lock.json | 533 +++++++++++++++++-
lastfm | 1 -
119 files changed, 7661 insertions(+), 20 deletions(-)
delete mode 100644 .gitmodules
create mode 100644 IF.Lastfm.Core/Api/AlbumApi.cs
create mode 100644 IF.Lastfm.Core/Api/ArtistApi.cs
create mode 100644 IF.Lastfm.Core/Api/ChartApi.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Album/AddShoutCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Album/GetInfoCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Album/GetShoutsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Album/GetTagsByUserCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Album/GetTopTagsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Album/SearchCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/AddShoutCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/GetInfoCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/GetShoutsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/GetSimilarCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/GetTagsByUserCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/GetTopAlbumsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/GetTopTagsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/GetTopTracksCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Artist/SearchCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Auth/GetMobileSessionCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Auth/GetSessionCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Auth/GetTokenCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Chart/GetTopArtistsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Chart/GetTopTagsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Chart/GetTopTracksCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/GetAsyncCommandBase.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/IAsyncCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/LastAsyncCommandBase.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Library/GetArtistsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Library/GetTracksCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Library/RemoveScrobbleCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Library/RemoveTrackCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/PostAsyncCommandBase.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Tag/GetInfoCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Tag/GetSimilarCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Tag/GetTopAlbumsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Tag/GetTopArtistsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Tag/GetTopTagsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Tag/GetTopTracksCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/AddShoutCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/GetInfoCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/GetShoutsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/GetSimilarCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/LoveCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/ScrobbleCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/SearchCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/UnloveCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/Track/UpdateNowPlayingCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/UnauthenticatedPostAsyncCommandBase.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/AddShoutCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetInfoCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetLovedTracksCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetRecentStationsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetRecentTracksCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetRecommendedArtistsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetShoutsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetTopAlbumsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetTopArtistsCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetWeeklyAlbumChartCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetWeeklyArtistChartCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetWeeklyChartListCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Commands/User/GetWeeklyTrackChartCommand.cs
create mode 100644 IF.Lastfm.Core/Api/Enums/LastResponseStatus.cs
create mode 100644 IF.Lastfm.Core/Api/Enums/LastStatsTimeSpan.cs
create mode 100644 IF.Lastfm.Core/Api/Helpers/ApiExtensions.cs
create mode 100644 IF.Lastfm.Core/Api/Helpers/ApiNameAttribute.cs
create mode 100644 IF.Lastfm.Core/Api/Helpers/LastResponse.cs
create mode 100644 IF.Lastfm.Core/Api/Helpers/LastResponse{T}.cs
create mode 100644 IF.Lastfm.Core/Api/Helpers/PageResponse.cs
create mode 100644 IF.Lastfm.Core/Api/IAlbumApi.cs
create mode 100644 IF.Lastfm.Core/Api/IArtistApi.cs
create mode 100644 IF.Lastfm.Core/Api/IChartApi.cs
create mode 100644 IF.Lastfm.Core/Api/ILastAuth.cs
create mode 100644 IF.Lastfm.Core/Api/ILibraryAPI.cs
create mode 100644 IF.Lastfm.Core/Api/ITagApi.cs
create mode 100644 IF.Lastfm.Core/Api/ITrackApi.cs
create mode 100644 IF.Lastfm.Core/Api/IUserApi.cs
create mode 100644 IF.Lastfm.Core/Api/LastAuth.cs
create mode 100644 IF.Lastfm.Core/Api/LastfmClient.cs
create mode 100644 IF.Lastfm.Core/Api/LibraryApi.cs
create mode 100644 IF.Lastfm.Core/Api/TagApi.cs
create mode 100644 IF.Lastfm.Core/Api/TrackApi.cs
create mode 100644 IF.Lastfm.Core/Api/UserApi.cs
create mode 100644 IF.Lastfm.Core/Helpers/ApiBase.cs
create mode 100644 IF.Lastfm.Core/Helpers/CountingHttpClientHandler.cs
create mode 100644 IF.Lastfm.Core/Helpers/EnumerableExtensions.cs
create mode 100644 IF.Lastfm.Core/Helpers/FakeResponseHandler.cs
create mode 100644 IF.Lastfm.Core/Helpers/QueueFakeResponseHandler.cs
create mode 100644 IF.Lastfm.Core/IF.Lastfm.Core.csproj
create mode 100644 IF.Lastfm.Core/Json/LastFmBooleanConverter.cs
create mode 100644 IF.Lastfm.Core/Json/PageResponseJsonConverter.cs
create mode 100644 IF.Lastfm.Core/LastFm.cs
create mode 100644 IF.Lastfm.Core/MD5.cs
create mode 100644 IF.Lastfm.Core/Objects/BuyLink.cs
create mode 100644 IF.Lastfm.Core/Objects/CountryCode.cs
create mode 100644 IF.Lastfm.Core/Objects/ILastFmObject.cs
create mode 100644 IF.Lastfm.Core/Objects/LastAlbum.cs
create mode 100644 IF.Lastfm.Core/Objects/LastArtist.cs
create mode 100644 IF.Lastfm.Core/Objects/LastImageSet.cs
create mode 100644 IF.Lastfm.Core/Objects/LastShout.cs
create mode 100644 IF.Lastfm.Core/Objects/LastStation.cs
create mode 100644 IF.Lastfm.Core/Objects/LastStats.cs
create mode 100644 IF.Lastfm.Core/Objects/LastTag.cs
create mode 100644 IF.Lastfm.Core/Objects/LastTrack.cs
create mode 100644 IF.Lastfm.Core/Objects/LastUser.cs
create mode 100644 IF.Lastfm.Core/Objects/LastUserSession.cs
create mode 100644 IF.Lastfm.Core/Objects/LastWeeklyChartList.cs
create mode 100644 IF.Lastfm.Core/Objects/LastWiki.cs
create mode 100644 IF.Lastfm.Core/Objects/Scrobble.cs
create mode 100644 IF.Lastfm.Core/Scrobblers/IScrobbler.cs
create mode 100644 IF.Lastfm.Core/Scrobblers/MemoryScrobbler.cs
create mode 100644 IF.Lastfm.Core/Scrobblers/ScrobbleResponse.cs
create mode 100644 IF.Lastfm.Core/Scrobblers/ScrobblerBase.cs
create mode 100644 IF.Lastfm.Core/packages.lock.json
delete mode 160000 lastfm
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index cc3083f0..00000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "lastfm"]
- path = lastfm
- url = https://github.com/tolbxela/lastfm.git
diff --git a/IF.Lastfm.Core/Api/AlbumApi.cs b/IF.Lastfm.Core/Api/AlbumApi.cs
new file mode 100644
index 00000000..3ae6402a
--- /dev/null
+++ b/IF.Lastfm.Core/Api/AlbumApi.cs
@@ -0,0 +1,102 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Commands.Album;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Helpers;
+using IF.Lastfm.Core.Objects;
+
+namespace IF.Lastfm.Core.Api
+{
+ public class AlbumApi : ApiBase, IAlbumApi
+ {
+ public AlbumApi(ILastAuth auth, HttpClient httpClient = null)
+ : base(httpClient)
+ {
+ Auth = auth;
+ }
+
+ public async Task> GetInfoAsync(string artistname, string albumname, bool autocorrect = false, string username = null)
+ {
+ var command = new GetInfoCommand(Auth, albumname, artistname)
+ {
+ Autocorrect = autocorrect,
+ HttpClient = HttpClient,
+ UserName = username
+ };
+
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> GetInfoByMbidAsync(string albumMbid, bool autocorrect = false, string username = null)
+ {
+ var command = new GetInfoCommand(Auth)
+ {
+ AlbumMbid = albumMbid,
+ Autocorrect = autocorrect,
+ HttpClient = HttpClient
+ };
+
+ return await command.ExecuteAsync();
+ }
+
+ //public Task> GetBuyLinksForAlbumAsync(string artist, string album, CountryCode country, bool autocorrect = false)
+ //{
+ // throw new NotImplementedException();
+ //}
+
+ public Task> GetTagsByUserAsync(string artist, string album, string username, bool autocorrect = false)
+ {
+ var command = new GetTagsByUserCommand(Auth, artist, album, username)
+ {
+ Autocorrect = autocorrect,
+ HttpClient = HttpClient
+ };
+
+ return command.ExecuteAsync();
+ }
+
+ public async Task> GetTopTagsAsync(string artist, string album, bool autocorrect = false)
+ {
+ var command = new GetTopTagsCommand(Auth)
+ {
+ ArtistName = artist,
+ AlbumName = album,
+ HttpClient = HttpClient
+ };
+
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> SearchAsync(string albumname, int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new SearchCommand(Auth, albumname)
+ {
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> GetShoutsAsync(string albumname, string artistname, bool autocorrect = false, int page = 1, int count = LastFm.DefaultPageLength)
+ {
+ var command = new GetShoutsCommand(Auth, albumname, artistname)
+ {
+ Page = page,
+ Autocorrect = autocorrect,
+ Count = count,
+ HttpClient = HttpClient
+ };
+
+ return await command.ExecuteAsync();
+ }
+
+ //public async Task AddShoutAsync(string albumname, string artistname, string message)
+ //{
+ // var command = new AddShoutCommand(Auth, albumname, artistname, message);
+
+ // return await command.ExecuteAsync();
+ //}
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/ArtistApi.cs b/IF.Lastfm.Core/Api/ArtistApi.cs
new file mode 100644
index 00000000..bdb9a457
--- /dev/null
+++ b/IF.Lastfm.Core/Api/ArtistApi.cs
@@ -0,0 +1,177 @@
+using System.Net.Http;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Commands.Artist;
+using IF.Lastfm.Core.Helpers;
+
+namespace IF.Lastfm.Core.Api
+{
+ public class ArtistApi : ApiBase, IArtistApi
+ {
+ public ArtistApi(ILastAuth auth, HttpClient httpClient = null)
+ : base(httpClient)
+ {
+ Auth = auth;
+ }
+
+
+
+ public async Task> GetInfoAsync(string artist, string bioLang = LastFm.DefaultLanguageCode, bool autocorrect = false)
+ {
+ var command = new GetInfoCommand(Auth)
+ {
+ ArtistName = artist,
+ BioLanguage = bioLang,
+ Autocorrect = autocorrect,
+ HttpClient = HttpClient
+ };
+
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> GetInfoByMbidAsync(string mbid, string bioLang = LastFm.DefaultLanguageCode, bool autocorrect = false)
+ {
+ var command = new GetInfoCommand(Auth)
+ {
+ ArtistMbid = mbid,
+ BioLanguage = bioLang,
+ Autocorrect = autocorrect,
+ HttpClient = HttpClient
+ };
+
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> GetTopAlbumsAsync(string artist, bool autocorrect = false, int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new GetTopAlbumsCommand(Auth)
+ {
+ ArtistName = artist,
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> GetTopAlbumsByMbidAsync(string mbid, bool autocorrect = false, int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new GetTopAlbumsCommand(Auth)
+ {
+ ArtistMbid = mbid,
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> GetTopTracksAsync(string artist, bool autocorrect = false, int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new GetTopTracksCommand(Auth, artist)
+ {
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> GetSimilarAsync(string artistname, bool autocorrect = false, int limit = LastFm.DefaultPageLength)
+ {
+ var command = new GetSimilarCommand(Auth)
+ {
+ ArtistName = artistname,
+ Autocorrect = autocorrect,
+ Limit = limit,
+ HttpClient = HttpClient
+ };
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> GetSimilarByMbidAsync(string mbid, bool autocorrect = false, int limit = LastFm.DefaultPageLength)
+ {
+ var command = new GetSimilarCommand(Auth)
+ {
+ ArtistMbid = mbid,
+ Autocorrect = autocorrect,
+ Limit = limit,
+ HttpClient = HttpClient
+ };
+ return await command.ExecuteAsync();
+ }
+
+ public Task> GetTagsByUserAsync(string artist, string username, bool autocorrect = false, int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new GetTagsByUserCommand(Auth, artist, username)
+ {
+ Autocorrect = autocorrect,
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+
+ return command.ExecuteAsync();
+ }
+
+ public Task> GetTopTagsAsync(string artist, bool autocorrect = false)
+ {
+ var command = new GetTopTagsCommand(Auth)
+ {
+ ArtistName = artist,
+ Autocorrect = autocorrect,
+ HttpClient = HttpClient
+ };
+
+ return command.ExecuteAsync();
+ }
+
+ public Task> GetTopTagsByMbidAsync(string mbid, bool autocorrect = false)
+ {
+ var command = new GetTopTagsCommand(Auth)
+ {
+ ArtistMbid = mbid,
+ Autocorrect = autocorrect,
+ HttpClient = HttpClient
+ };
+
+ return command.ExecuteAsync();
+ }
+
+
+ public async Task> GetShoutsAsync(string artist, int page = 0, int count = LastFm.DefaultPageLength, bool autocorrect = false)
+ {
+ var command = new GetShoutsCommand(Auth, artist)
+ {
+ Autocorrect = autocorrect,
+ Page = page,
+ Count = count,
+ HttpClient = HttpClient
+ };
+ return await command.ExecuteAsync();
+ }
+
+ public async Task AddShoutAsync(string artistname, string message)
+ {
+ var command = new AddShoutCommand(Auth, artistname, message)
+ {
+ HttpClient = HttpClient
+ };
+
+ return await command.ExecuteAsync();
+ }
+
+ public async Task> SearchAsync(string artistname, int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new SearchCommand(Auth, artistname)
+ {
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+
+ return await command.ExecuteAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/ChartApi.cs b/IF.Lastfm.Core/Api/ChartApi.cs
new file mode 100644
index 00000000..579b7c77
--- /dev/null
+++ b/IF.Lastfm.Core/Api/ChartApi.cs
@@ -0,0 +1,70 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Commands.Chart;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Helpers;
+using IF.Lastfm.Core.Objects;
+
+namespace IF.Lastfm.Core.Api
+{
+ public class ChartApi : ApiBase, IChartApi
+ {
+
+ public ChartApi(ILastAuth auth, HttpClient httpClient = null)
+ : base(httpClient)
+ {
+ Auth = auth;
+ }
+
+ ///
+ /// Get a list of the most-scrobbled artists on Last.fm.
+ ///
+ ///
+ /// Bug 28/05/16 - itemsPerPage parameter doesn't seem to work all the time; certain values cause more or fewer items to be returned
+ ///
+ public Task> GetTopArtistsAsync(int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new GetTopArtistsCommand(Auth)
+ {
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+ return command.ExecuteAsync();
+ }
+
+ ///
+ /// Get a list of the most-scrobbled tracks on Last.fm.
+ ///
+ ///
+ /// Bug 28/05/16 - itemsPerPage parameter doesn't seem to work all the time; certain values cause more or fewer items to be returned
+ ///
+ public Task> GetTopTracksAsync(int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new GetTopTracksCommand(Auth)
+ {
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+ return command.ExecuteAsync();
+ }
+
+ ///
+ /// Get a list of the most frequently used tags by Last.fm users
+ ///
+ ///
+ /// Bug 28/05/16 - page and itemsPerPage parameters do not actually affect the number of or selection of tags returned
+ ///
+ public Task> GetTopTagsAsync(int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
+ {
+ var command = new GetTopTagsCommand(Auth)
+ {
+ Page = page,
+ Count = itemsPerPage,
+ HttpClient = HttpClient
+ };
+ return command.ExecuteAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Album/AddShoutCommand.cs b/IF.Lastfm.Core/Api/Commands/Album/AddShoutCommand.cs
new file mode 100644
index 00000000..0a9518cc
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Album/AddShoutCommand.cs
@@ -0,0 +1,36 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Helpers;
+
+namespace IF.Lastfm.Core.Api.Commands.Album
+{
+ [ApiMethodName("album.shout")]
+ internal class AddShoutCommand : PostAsyncCommandBase
+ {
+ public string Album { get; set; }
+
+ public string Artist { get; set; }
+
+ public string Message { get; set; }
+
+ public AddShoutCommand(ILastAuth auth, string album, string artist, string message)
+ : base(auth)
+ {
+ Album = album;
+ Artist = artist;
+ Message = message;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("album", Album);
+ Parameters.Add("artist", Artist);
+ Parameters.Add("message", Message);
+ }
+
+ public override async Task HandleResponse(HttpResponseMessage response)
+ {
+ return await LastResponse.HandleResponse(response);
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Album/GetInfoCommand.cs b/IF.Lastfm.Core/Api/Commands/Album/GetInfoCommand.cs
new file mode 100644
index 00000000..ca552045
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Album/GetInfoCommand.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Album
+{
+ [ApiMethodName("album.getInfo")]
+ internal class GetInfoCommand : GetAsyncCommandBase>
+ {
+ public string AlbumMbid { get; set; }
+
+ public string ArtistName { get; set; }
+
+ public string AlbumName { get; set; }
+
+ public string UserName { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetInfoCommand(ILastAuth auth) : base(auth) { }
+
+ public GetInfoCommand(ILastAuth auth, string album, string artist)
+ : this(auth)
+ {
+ AlbumName = album;
+ ArtistName = artist;
+ }
+
+ public override void SetParameters()
+ {
+ if (AlbumMbid != null)
+ {
+ Parameters.Add("mbid", AlbumMbid);
+ }
+ else
+ {
+ Parameters.Add("artist", ArtistName);
+ Parameters.Add("album", AlbumName);
+ }
+
+ if (UserName != null)
+ {
+ Parameters.Add("username", UserName);
+ }
+
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var album = LastAlbum.ParseJToken(jtoken.SelectToken("album"));
+
+ return LastResponse.CreateSuccessResponse(album);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Album/GetShoutsCommand.cs b/IF.Lastfm.Core/Api/Commands/Album/GetShoutsCommand.cs
new file mode 100644
index 00000000..a143074a
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Album/GetShoutsCommand.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Album
+{
+ [ApiMethodName("album.getShouts")]
+ internal class GetShoutsCommand : GetAsyncCommandBase>
+ {
+ public string AlbumName { get; set; }
+
+ public string ArtistName { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetShoutsCommand(ILastAuth auth, string albumname, string artistname)
+ : base(auth)
+ {
+ AlbumName = albumname;
+ ArtistName = artistname;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("album", AlbumName);
+ Parameters.Add("artist", ArtistName);
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json).SelectToken("shouts");
+ var itemsToken = jtoken.SelectToken("shout");
+ var pageInfoToken = jtoken.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, pageInfoToken, LastShout.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Album/GetTagsByUserCommand.cs b/IF.Lastfm.Core/Api/Commands/Album/GetTagsByUserCommand.cs
new file mode 100644
index 00000000..9760eaf7
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Album/GetTagsByUserCommand.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Album
+{
+ [ApiMethodName("album.getTags")]
+ internal class GetTagsByUserCommand : GetAsyncCommandBase>
+ {
+ public string ArtistName { get; set; }
+
+ public string AlbumName { get; set; }
+
+ public string Username { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetTagsByUserCommand(ILastAuth auth, string artist, string album, string username)
+ : base(auth)
+ {
+ ArtistName = artist;
+ AlbumName = album;
+ Username = username;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("artist", ArtistName);
+ Parameters.Add("album", AlbumName);
+ Parameters.Add("user", Username);
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("tags");
+ var itemsToken = resultsToken.SelectToken("tag");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, token => LastTag.ParseJToken(token));
+ }
+ else
+ {
+ return PageResponse.CreateErrorResponse(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Album/GetTopTagsCommand.cs b/IF.Lastfm.Core/Api/Commands/Album/GetTopTagsCommand.cs
new file mode 100644
index 00000000..e7fd0370
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Album/GetTopTagsCommand.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Album
+{
+ [ApiMethodName("album.getTopTags")]
+ internal class GetTopTagsCommand : GetAsyncCommandBase>
+ {
+ public string AlbumMbid { get; set; }
+
+ public string ArtistName { get; set; }
+
+ public string AlbumName { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetTopTagsCommand(ILastAuth auth) : base(auth) { }
+
+ public GetTopTagsCommand(ILastAuth auth, string album, string artist)
+ : this(auth)
+ {
+ AlbumName = album;
+ ArtistName = artist;
+ }
+
+ public override void SetParameters()
+ {
+ if (AlbumMbid != null)
+ {
+ Parameters.Add("mbid", AlbumMbid);
+ }
+ else
+ {
+ Parameters.Add("artist", ArtistName);
+ Parameters.Add("album", AlbumName);
+ }
+
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("toptags");
+ var itemsToken = resultsToken.SelectToken("tag");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, resultsToken, token => LastTag.ParseJToken(token), LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Album/SearchCommand.cs b/IF.Lastfm.Core/Api/Commands/Album/SearchCommand.cs
new file mode 100644
index 00000000..1060b798
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Album/SearchCommand.cs
@@ -0,0 +1,49 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Album
+{
+ [ApiMethodName("album.search")]
+ internal class SearchCommand : GetAsyncCommandBase>
+ {
+ public string AlbumName { get; set; }
+
+ public SearchCommand(ILastAuth auth, string albumName)
+ : base(auth)
+ {
+ AlbumName = albumName;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("album", AlbumName);
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("results");
+ var itemsToken = resultsToken.SelectToken("albummatches").SelectToken("album");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, resultsToken, LastAlbum.ParseJToken, LastPageResultsType.OpenQuery);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/AddShoutCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/AddShoutCommand.cs
new file mode 100644
index 00000000..bdc27223
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/AddShoutCommand.cs
@@ -0,0 +1,31 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Helpers;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.shout")]
+ internal class AddShoutCommand : PostAsyncCommandBase
+ {
+ public string Artist { get; set; }
+
+ public string Message { get; set; }
+
+ public AddShoutCommand(ILastAuth auth, string artist, string message) : base(auth)
+ {
+ Artist = artist;
+ Message = message;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("artist", Artist);
+ Parameters.Add("message", Message);
+ }
+
+ public override async Task HandleResponse(HttpResponseMessage response)
+ {
+ return await LastResponse.HandleResponse(response);
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/GetInfoCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/GetInfoCommand.cs
new file mode 100644
index 00000000..9b9b8ab5
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/GetInfoCommand.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.getInfo")]
+ internal class GetInfoCommand : GetAsyncCommandBase>
+ {
+ public string ArtistMbid { get; set; }
+
+ public string ArtistName { get; set; }
+
+ public string BioLanguage { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetInfoCommand(ILastAuth auth) : base(auth) { }
+
+ ///
+ /// TODO Bio language
+ ///
+ public override void SetParameters()
+ {
+ if (ArtistMbid != null)
+ {
+ Parameters.Add("mbid", ArtistMbid);
+ }
+ else
+ {
+ Parameters.Add("artist", ArtistName);
+ }
+
+ if (BioLanguage != null)
+ {
+ Parameters.Add("lang", BioLanguage);
+ }
+
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var artist = LastArtist.ParseJToken(jtoken.SelectToken("artist"));
+
+ return LastResponse.CreateSuccessResponse(artist);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/GetShoutsCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/GetShoutsCommand.cs
new file mode 100644
index 00000000..709220dd
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/GetShoutsCommand.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.getShouts")]
+ internal class GetShoutsCommand : GetAsyncCommandBase>
+ {
+ public string ArtistName { get; set; }
+ public bool Autocorrect { get; set; }
+
+ public GetShoutsCommand(ILastAuth auth, string artistname)
+ : base(auth)
+ {
+ ArtistName = artistname;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("artist", ArtistName);
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var shoutsToken = jtoken.SelectToken("shouts");
+ var itemsToken = shoutsToken.SelectToken("shout");
+ var pageInfoToken = shoutsToken.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, pageInfoToken, LastShout.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/GetSimilarCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/GetSimilarCommand.cs
new file mode 100644
index 00000000..e9f9b023
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/GetSimilarCommand.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.getSimilar")]
+ internal class GetSimilarCommand : GetAsyncCommandBase>
+ {
+ public bool Autocorrect { get; set; }
+
+ public string ArtistMbid { get; set; }
+
+ public string ArtistName { get; set; }
+
+ public int? Limit { get; set; }
+
+ public GetSimilarCommand(ILastAuth auth)
+ : base(auth){}
+
+
+ public override void SetParameters()
+ {
+
+ if (ArtistMbid != null)
+ {
+ Parameters.Add("mbid", ArtistMbid);
+ }
+ else
+ {
+ Parameters.Add("artist", ArtistName);
+ }
+
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ if (Limit != null)
+ {
+ Parameters.Add("limit", Limit.ToString());
+ }
+
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var itemsToken = jtoken.SelectToken("similarartists").SelectToken("artist");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, LastArtist.ParseJToken);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/GetTagsByUserCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/GetTagsByUserCommand.cs
new file mode 100644
index 00000000..c447f74a
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/GetTagsByUserCommand.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.getTags")]
+ internal class GetTagsByUserCommand : GetAsyncCommandBase>
+ {
+ public string ArtistName { get; set; }
+
+ public string Username { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetTagsByUserCommand(ILastAuth auth, string artist, string username)
+ : base(auth)
+ {
+ ArtistName = artist;
+ Username = username;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("artist", ArtistName);
+ Parameters.Add("user", Username);
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("tags");
+ var itemsToken = resultsToken.SelectToken("tag");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, token => LastTag.ParseJToken(token));
+ }
+ else
+ {
+ return PageResponse.CreateErrorResponse(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/GetTopAlbumsCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/GetTopAlbumsCommand.cs
new file mode 100644
index 00000000..8e0f6e12
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/GetTopAlbumsCommand.cs
@@ -0,0 +1,54 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.getTopAlbums")]
+ internal class GetTopAlbumsCommand : GetAsyncCommandBase>
+ {
+ public string ArtistName { get; set; }
+ public string ArtistMbid { get; set; }
+
+ public GetTopAlbumsCommand(ILastAuth auth )
+ : base(auth) { }
+
+ public override void SetParameters()
+ {
+ if (ArtistMbid != null)
+ {
+ Parameters.Add("mbid", ArtistMbid);
+ }
+ else
+ {
+ Parameters.Add("artist", ArtistName);
+ }
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var albumsToken = jtoken.SelectToken("topalbums");
+ var itemsToken = albumsToken.SelectToken("album");
+ var pageInfoToken = albumsToken.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, pageInfoToken, LastAlbum.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/GetTopTagsCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/GetTopTagsCommand.cs
new file mode 100644
index 00000000..52f1f468
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/GetTopTagsCommand.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.getTopTags")]
+ internal class GetTopTagsCommand : GetAsyncCommandBase>
+ {
+ public string ArtistMbid { get; set; }
+
+ public string ArtistName { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetTopTagsCommand(ILastAuth auth) : base(auth)
+ {
+ }
+
+ public override void SetParameters()
+ {
+ var hasMbid = !string.IsNullOrEmpty(ArtistMbid);
+ var hasName = !string.IsNullOrEmpty(ArtistName);
+
+ if (!hasMbid && !hasName)
+ {
+ throw new InvalidOperationException($"Either {nameof(ArtistMbid)} or {nameof(ArtistName)} must be set");
+ }
+
+ if (hasMbid && hasName)
+ {
+ throw new InvalidOperationException($"");
+ }
+
+ if (hasMbid)
+ {
+ Parameters.Add("mbid", ArtistMbid);
+ }
+ else
+ {
+ Parameters.Add("artist", ArtistName);
+ }
+
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("toptags");
+ var itemsToken = resultsToken.SelectToken("tag");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, LastTag.ParseJToken);
+ }
+ else
+ {
+ return PageResponse.CreateErrorResponse(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/GetTopTracksCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/GetTopTracksCommand.cs
new file mode 100644
index 00000000..84e29550
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/GetTopTracksCommand.cs
@@ -0,0 +1,50 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.getTopTracks")]
+ internal class GetTopTracksCommand : GetAsyncCommandBase>
+ {
+ public string ArtistName { get; set; }
+
+ public GetTopTracksCommand(ILastAuth auth, string artistname)
+ : base(auth)
+ {
+ ArtistName = artistname;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("artist", ArtistName);
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var tracksToken = jtoken.SelectToken("toptracks");
+ var itemsToken = tracksToken.SelectToken("track");
+ var pageInfoToken = tracksToken.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, pageInfoToken, LastTrack.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Artist/SearchCommand.cs b/IF.Lastfm.Core/Api/Commands/Artist/SearchCommand.cs
new file mode 100644
index 00000000..e6f936b9
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Artist/SearchCommand.cs
@@ -0,0 +1,49 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Artist
+{
+ [ApiMethodName("artist.search")]
+ internal class SearchCommand : GetAsyncCommandBase>
+ {
+ public string ArtistName { get; set; }
+
+ public SearchCommand(ILastAuth auth, string artistName)
+ : base(auth)
+ {
+ ArtistName = artistName;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("artist", ArtistName);
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("results");
+ var itemsToken = resultsToken.SelectToken("artistmatches").SelectToken("artist");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, resultsToken, LastArtist.ParseJToken, LastPageResultsType.OpenQuery);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Auth/GetMobileSessionCommand.cs b/IF.Lastfm.Core/Api/Commands/Auth/GetMobileSessionCommand.cs
new file mode 100644
index 00000000..86431caf
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Auth/GetMobileSessionCommand.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Auth
+{
+ [ApiMethodName("auth.getMobileSession")]
+ internal class GetMobileSessionCommand : UnauthenticatedPostAsyncCommandBase>
+ {
+ public string Username { get; set; }
+
+ public string Password { get; set; }
+
+ public GetMobileSessionCommand(ILastAuth auth, string username, string password) : base(auth)
+ {
+ Username = username;
+ Password = password;
+ }
+
+ protected override Uri BuildRequestUrl()
+ {
+ return new Uri(LastFm.ApiRootSsl, UriKind.Absolute);
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("username", Username);
+ Parameters.Add("password", Password);
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var sessionObject = JsonConvert.DeserializeObject(json).GetValue("session");
+ var session = JsonConvert.DeserializeObject(sessionObject.ToString());
+
+ return LastResponse.CreateSuccessResponse(session);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Auth/GetSessionCommand.cs b/IF.Lastfm.Core/Api/Commands/Auth/GetSessionCommand.cs
new file mode 100644
index 00000000..081fb58c
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Auth/GetSessionCommand.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Auth
+{
+ [ApiMethodName("auth.getSession")]
+ internal class GetSessionCommand : UnauthenticatedPostAsyncCommandBase>
+ {
+ private string Token { get; }
+
+ public GetSessionCommand(ILastAuth auth, string authToken) : base(auth)
+ {
+ Token = authToken;
+ }
+
+ protected override Uri BuildRequestUrl()
+ {
+ return new Uri(LastFm.ApiRootSsl, UriKind.Absolute);
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("token", Token);
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ if (LastFm.IsResponseValid(json, out LastResponseStatus status) && response.IsSuccessStatusCode)
+ {
+ var sessionObject = JsonConvert.DeserializeObject(json).GetValue("session");
+ var session = JsonConvert.DeserializeObject(sessionObject.ToString());
+
+ return LastResponse.CreateSuccessResponse(session);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Auth/GetTokenCommand.cs b/IF.Lastfm.Core/Api/Commands/Auth/GetTokenCommand.cs
new file mode 100644
index 00000000..b0ebf06d
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Auth/GetTokenCommand.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Auth
+{
+ [ApiMethodName("auth.getToken")]
+ internal class GetTokenCommand : UnauthenticatedPostAsyncCommandBase>
+ {
+ public GetTokenCommand(ILastAuth auth) : base(auth)
+ {
+ }
+
+ protected override Uri BuildRequestUrl()
+ {
+ return new Uri(LastFm.ApiRootSsl, UriKind.Absolute);
+ }
+
+ public override void SetParameters()
+ {
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ if (LastFm.IsResponseValid(json, out LastResponseStatus status) && response.IsSuccessStatusCode)
+ {
+ var token = JsonConvert.DeserializeObject(json).GetValue("token");
+ return LastResponse.CreateSuccessResponse(token.Value());
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Chart/GetTopArtistsCommand.cs b/IF.Lastfm.Core/Api/Commands/Chart/GetTopArtistsCommand.cs
new file mode 100644
index 00000000..d3b6c07f
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Chart/GetTopArtistsCommand.cs
@@ -0,0 +1,41 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Chart
+{
+ [ApiMethodName("chart.getTopArtists")]
+ internal class GetTopArtistsCommand : GetAsyncCommandBase>
+ {
+ public GetTopArtistsCommand(ILastAuth auth) : base(auth) { }
+
+ public override void SetParameters()
+ {
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json).SelectToken("artists");
+ var itemsToken = jtoken.SelectToken("artist");
+ var pageInfoToken = jtoken.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, pageInfoToken, LastArtist.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Chart/GetTopTagsCommand.cs b/IF.Lastfm.Core/Api/Commands/Chart/GetTopTagsCommand.cs
new file mode 100644
index 00000000..2aa38c8f
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Chart/GetTopTagsCommand.cs
@@ -0,0 +1,43 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Chart
+{
+ [ApiMethodName("chart.getTopTags")]
+ internal class GetTopTagsCommand : GetAsyncCommandBase>
+ {
+ public GetTopTagsCommand(ILastAuth auth) : base(auth)
+ {
+ }
+
+ public override void SetParameters()
+ {
+ // 28/05/16 Paging parameters don't actually seem to do anything
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jo = JObject.Parse(json);
+ var tagsToken = jo.SelectToken("tags.tag");
+ var pageInfoToken = jo.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(tagsToken, pageInfoToken, LastTag.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return PageResponse.CreateErrorResponse(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Chart/GetTopTracksCommand.cs b/IF.Lastfm.Core/Api/Commands/Chart/GetTopTracksCommand.cs
new file mode 100644
index 00000000..dbd30cc1
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Chart/GetTopTracksCommand.cs
@@ -0,0 +1,42 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Chart
+{
+ [ApiMethodName("chart.getTopTracks")]
+ internal class GetTopTracksCommand : GetAsyncCommandBase>
+ {
+ public GetTopTracksCommand(ILastAuth auth) : base(auth) { }
+
+ public override void SetParameters()
+ {
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var tracksToken = jtoken.SelectToken("tracks");
+ var itemsToken = tracksToken.SelectToken("track");
+ var pageInfoToken = tracksToken.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, pageInfoToken, LastTrack.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/GetAsyncCommandBase.cs b/IF.Lastfm.Core/Api/Commands/GetAsyncCommandBase.cs
new file mode 100644
index 00000000..04f7d43f
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/GetAsyncCommandBase.cs
@@ -0,0 +1,53 @@
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace IF.Lastfm.Core.Api.Commands
+{
+ public abstract class GetAsyncCommandBase : LastAsyncCommandBase where T : LastResponse, new()
+ {
+ protected GetAsyncCommandBase(ILastAuth auth)
+ {
+ Auth = auth;
+ }
+
+ public override async Task ExecuteAsync()
+ {
+ SetParameters();
+
+ EscapeParameters();
+
+ Url = BuildRequestUrl();
+
+ try
+ {
+ var httpClient = HttpClient;
+ using (var response = await httpClient.GetAsync(Url))
+ {
+ return await HandleResponse(response);
+ }
+ }
+ catch (HttpRequestException)
+ {
+ return LastResponse.CreateErrorResponse(LastResponseStatus.RequestFailed);
+ }
+ }
+
+ protected override Uri BuildRequestUrl()
+ {
+ var apiUrl = LastFm.FormatApiUrl(Method, Auth.ApiKey, Parameters);
+ return new Uri(apiUrl, UriKind.Absolute);
+ }
+
+ private void EscapeParameters()
+ {
+ foreach (var key in Parameters.Keys.ToList())
+ {
+ Parameters[key] = Uri.EscapeDataString(Parameters[key]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/IAsyncCommand.cs b/IF.Lastfm.Core/Api/Commands/IAsyncCommand.cs
new file mode 100644
index 00000000..b5260bd8
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/IAsyncCommand.cs
@@ -0,0 +1,10 @@
+using IF.Lastfm.Core.Api.Helpers;
+using System.Threading.Tasks;
+
+namespace IF.Lastfm.Core.Api.Commands
+{
+ public interface IAsyncCommand where T : LastResponse, new()
+ {
+ Task ExecuteAsync();
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/LastAsyncCommandBase.cs b/IF.Lastfm.Core/Api/Commands/LastAsyncCommandBase.cs
new file mode 100644
index 00000000..2067fbe2
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/LastAsyncCommandBase.cs
@@ -0,0 +1,93 @@
+using IF.Lastfm.Core.Api.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Reflection;
+
+namespace IF.Lastfm.Core.Api.Commands
+{
+ ///
+ /// Having this type makes reflection easier - there probably isn't any other need for it
+ ///
+ public abstract class LastAsyncCommandBase
+ {
+ private string _methodName;
+
+ public string Method
+ {
+ get
+ {
+ if (!String.IsNullOrEmpty(_methodName))
+ {
+ return _methodName;
+ }
+
+ var methodNameAttribute = this.GetType().GetTypeInfo().GetCustomAttribute();
+ if (methodNameAttribute == null)
+ {
+ throw new NotImplementedException(@"Could not find an ApiMethodNameAttribute on the current Command implementation.
+This custom attribute must be present on all Commands. For more information, see the ApiMethodNameAttribute documentation.");
+ }
+ return methodNameAttribute.Text;
+ }
+ internal set { _methodName = value; }
+ }
+
+ public Dictionary Parameters { get; set; }
+
+ ///
+ /// The HttpClient used for the request.
+ ///
+ public HttpClient HttpClient { get; set; }
+ }
+
+ public abstract class LastAsyncCommandBase : LastAsyncCommandBase, IAsyncCommand where T : LastResponse, new()
+ {
+ public ILastAuth Auth { get; protected set; }
+
+ private int _page;
+
+ public int Page
+ {
+ get => _page == 0 ? LastFm.DefaultPage : _page;
+ set
+ {
+ if (value < 1) throw new ArgumentOutOfRangeException(nameof(value), "Page property cannot be less than 1");
+ _page = value;
+ }
+ }
+
+ public int Count { get; set; }
+
+ public Uri Url { get; protected set; }
+
+ protected LastAsyncCommandBase()
+ {
+ Parameters = new Dictionary();
+ }
+
+ public abstract void SetParameters();
+
+ protected abstract Uri BuildRequestUrl();
+
+ protected void AddPagingParameters()
+ {
+ Parameters.Add("page", Page.ToString());
+ Parameters.Add("limit", Count.ToString());
+ }
+
+ ///
+ /// Annoying workaround for Windows Phone's caching...
+ /// see http://stackoverflow.com/questions/6334788/windows-phone-7-webrequest-caching
+ ///
+ protected void DisableCaching()
+ {
+ Parameters.Add("disablecachetoken", DateTime.UtcNow.Ticks.ToString());
+ }
+
+ public abstract Task ExecuteAsync();
+
+ public abstract Task HandleResponse(HttpResponseMessage response);
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Library/GetArtistsCommand.cs b/IF.Lastfm.Core/Api/Commands/Library/GetArtistsCommand.cs
new file mode 100644
index 00000000..0d0ce1ad
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Library/GetArtistsCommand.cs
@@ -0,0 +1,49 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Library
+{
+ [ApiMethodName("library.getArtists")]
+ internal class GetArtistsCommand : GetAsyncCommandBase>
+ {
+ public string Username { get; }
+
+ public GetArtistsCommand(ILastAuth auth, string username) : base(auth)
+ {
+ Username = username;
+ Page = 1;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("user", Username);
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json).SelectToken("artists");
+ var tracksToken = jtoken.SelectToken("artist");
+ var pageInfoToken = jtoken.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(tracksToken, pageInfoToken, LastArtist.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Library/GetTracksCommand.cs b/IF.Lastfm.Core/Api/Commands/Library/GetTracksCommand.cs
new file mode 100644
index 00000000..5cf85c31
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Library/GetTracksCommand.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Library
+{
+ [ApiMethodName("library.getTracks")]
+ internal class GetTracksCommand : GetAsyncCommandBase>
+ {
+ public string Username { get; }
+
+ public string Artist { get; }
+
+ public string Album { get; }
+
+ public DateTimeOffset Since { get; }
+
+ public GetTracksCommand(ILastAuth auth, string username, string artist, string album, DateTimeOffset since)
+ : base(auth)
+ {
+ Username = username;
+ Artist = artist;
+ Album = album;
+ Since = since;
+ Page = 1;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("user", Username);
+ Parameters.Add("artist", Artist);
+ Parameters.Add("album", Album);
+ Parameters.Add("from", Since.AsUnixTime().ToString());
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json).SelectToken("tracks");
+ var tracksToken = jtoken.SelectToken("track");
+ var pageInfoToken = jtoken.SelectToken("@attr");
+
+ return PageResponse.CreateSuccessResponse(tracksToken, pageInfoToken, LastTrack.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Library/RemoveScrobbleCommand.cs b/IF.Lastfm.Core/Api/Commands/Library/RemoveScrobbleCommand.cs
new file mode 100644
index 00000000..7a0ca530
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Library/RemoveScrobbleCommand.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Helpers;
+
+namespace IF.Lastfm.Core.Api.Commands.Library {
+ [ApiMethodName("library.removeScrobble")]
+ internal class RemoveScrobbleCommand : PostAsyncCommandBase {
+ public string Artist { get; set; }
+
+ public string Track { get; set; }
+ public DateTimeOffset Timestamp { get; set; }
+
+ public RemoveScrobbleCommand( ILastAuth auth, string artist, string track, DateTimeOffset timestamp ) : base( auth ) {
+ Artist = artist;
+ Track = track;
+ Timestamp = timestamp;
+ }
+
+
+ public override void SetParameters() {
+ Parameters.Add( "artist", Artist );
+ Parameters.Add( "track", Track );
+ Parameters.Add( "timestamp", Timestamp.AsUnixTime().ToString() );
+ }
+
+ public override async Task HandleResponse( HttpResponseMessage response ) {
+ return await LastResponse.HandleResponse( response );
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Library/RemoveTrackCommand.cs b/IF.Lastfm.Core/Api/Commands/Library/RemoveTrackCommand.cs
new file mode 100644
index 00000000..4633aaef
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Library/RemoveTrackCommand.cs
@@ -0,0 +1,27 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Helpers;
+
+namespace IF.Lastfm.Core.Api.Commands.Library {
+ [ApiMethodName("library.removeTrack")]
+ internal class RemoveTrackCommand : PostAsyncCommandBase {
+ public string Artist { get; set; }
+
+ public string Track { get; set; }
+
+ public RemoveTrackCommand( ILastAuth auth, string artist, string track) : base( auth ) {
+ Artist = artist;
+ Track = track;
+ }
+
+
+ public override void SetParameters() {
+ Parameters.Add( "artist", Artist );
+ Parameters.Add( "track", Track );
+ }
+
+ public override async Task HandleResponse( HttpResponseMessage response ) {
+ return await LastResponse.HandleResponse( response );
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/PostAsyncCommandBase.cs b/IF.Lastfm.Core/Api/Commands/PostAsyncCommandBase.cs
new file mode 100644
index 00000000..a3183eea
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/PostAsyncCommandBase.cs
@@ -0,0 +1,65 @@
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace IF.Lastfm.Core.Api.Commands
+{
+ public abstract class PostAsyncCommandBase : LastAsyncCommandBase where T : LastResponse, new()
+ {
+ protected PostAsyncCommandBase(ILastAuth auth)
+ {
+ Auth = auth;
+ }
+
+ protected override Uri BuildRequestUrl()
+ {
+ return new Uri(LastFm.ApiRoot, UriKind.Absolute);
+ }
+
+ public override Task ExecuteAsync()
+ {
+ if (!Auth.Authenticated)
+ {
+ return Task.FromResult(LastResponse.CreateErrorResponse(LastResponseStatus.BadAuth));
+ }
+
+ return ExecuteAsyncInternal();
+ }
+
+ protected async Task ExecuteAsyncInternal()
+ {
+ SetParameters();
+
+ var toRemove = Parameters.Where(p => String.IsNullOrEmpty(p.Value)).ToList();
+ foreach (var parameter in toRemove)
+ {
+ Parameters.Remove(parameter.Key);
+ }
+
+ Url = BuildRequestUrl();
+
+ var apisig = Auth.GenerateMethodSignature(Method, Parameters);
+
+ var postContent = LastFm.CreatePostBody(Method,
+ Auth.ApiKey,
+ apisig,
+ Parameters);
+
+ try
+ {
+ var httpClient = HttpClient;
+ using (var response = await httpClient.PostAsync(Url, postContent))
+ {
+ return await HandleResponse(response);
+ }
+ }
+ catch (HttpRequestException)
+ {
+ return LastResponse.CreateErrorResponse(LastResponseStatus.RequestFailed);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Tag/GetInfoCommand.cs b/IF.Lastfm.Core/Api/Commands/Tag/GetInfoCommand.cs
new file mode 100644
index 00000000..72ba67d1
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Tag/GetInfoCommand.cs
@@ -0,0 +1,47 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Tag
+{
+ [ApiMethodName("tag.getInfo")]
+ public class GetInfoCommand:GetAsyncCommandBase>
+ {
+ public string TagName { get; set; }
+
+ public GetInfoCommand(ILastAuth auth, string tagName)
+ : base(auth)
+ {
+ TagName = tagName;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("tag", TagName);
+
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var tag = LastTag.ParseJToken(jtoken.SelectToken("tag"));
+
+ return LastResponse.CreateSuccessResponse(tag);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Tag/GetSimilarCommand.cs b/IF.Lastfm.Core/Api/Commands/Tag/GetSimilarCommand.cs
new file mode 100644
index 00000000..80c469e6
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Tag/GetSimilarCommand.cs
@@ -0,0 +1,47 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Tag
+{
+ [ApiMethodName("tag.getSimilar")]
+ internal class GetSimilarCommand : GetAsyncCommandBase>
+ {
+ public string TagName { get; set; }
+
+ public GetSimilarCommand(ILastAuth auth, string tagname)
+ : base(auth)
+ {
+ TagName = tagname;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("tag", TagName);
+
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json).SelectToken("similartags");
+ var itemsToken = jtoken.SelectToken("tag");
+ var attrToken = jtoken.SelectToken("@attr");
+ var relatedTag = attrToken.SelectToken("tag").Value();
+
+ return PageResponse.CreateSuccessResponse(itemsToken, jt => LastTag.ParseJToken(jt, relatedTag));
+ }
+
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Tag/GetTopAlbumsCommand.cs b/IF.Lastfm.Core/Api/Commands/Tag/GetTopAlbumsCommand.cs
new file mode 100644
index 00000000..aac843e2
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Tag/GetTopAlbumsCommand.cs
@@ -0,0 +1,47 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Tag
+{
+ [ApiMethodName("tag.getTopAlbums")]
+ internal class GetTopAlbumsCommand: GetAsyncCommandBase>
+ {
+ public string TagName { get; set; }
+
+ public GetTopAlbumsCommand(ILastAuth auth, string tagName) : base(auth)
+ {
+ TagName = tagName;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("tag", TagName);
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("topalbums");
+ var itemsToken = resultsToken.SelectToken("album");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, resultsToken, LastAlbum.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Tag/GetTopArtistsCommand.cs b/IF.Lastfm.Core/Api/Commands/Tag/GetTopArtistsCommand.cs
new file mode 100644
index 00000000..2efc4da7
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Tag/GetTopArtistsCommand.cs
@@ -0,0 +1,53 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Tag
+{
+ [ApiMethodName("tag.getTopArtists")]
+ internal class GetTopArtistsCommand : GetAsyncCommandBase>
+ {
+ public string TagName { get; set; }
+
+ public GetTopArtistsCommand(ILastAuth auth, string tagName) : base(auth)
+ {
+ TagName = tagName;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("tag", TagName);
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("topartists");
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ if (string.IsNullOrEmpty(resultsToken.SelectToken("@attr.tag").Value()))
+ {
+ return PageResponse.CreateErrorResponse(LastResponseStatus.MissingParameters);
+ }
+
+ var itemsToken = resultsToken.SelectToken("artist");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, resultsToken, LastArtist.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ // The tag api always returns a "valid" response, so
+ return PageResponse.CreateErrorResponse(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Tag/GetTopTagsCommand.cs b/IF.Lastfm.Core/Api/Commands/Tag/GetTopTagsCommand.cs
new file mode 100644
index 00000000..99d0ff04
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Tag/GetTopTagsCommand.cs
@@ -0,0 +1,40 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Tag
+{
+ [ApiMethodName("tag.getTopTags")]
+ public class GetTopTagsCommand : GetAsyncCommandBase>
+ {
+ public GetTopTagsCommand(ILastAuth auth)
+ : base(auth)
+ {
+ }
+
+ public override void SetParameters()
+ {
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var itemsToken = jtoken.SelectToken("toptags.tag");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, LastTag.ParseJToken);
+ }
+
+ return PageResponse.CreateErrorResponse(status);
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Tag/GetTopTracksCommand.cs b/IF.Lastfm.Core/Api/Commands/Tag/GetTopTracksCommand.cs
new file mode 100644
index 00000000..9ca4b664
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Tag/GetTopTracksCommand.cs
@@ -0,0 +1,47 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Tag
+{
+ [ApiMethodName("tag.getTopTracks")]
+ internal class GetTopTracksCommand : GetAsyncCommandBase>
+ {
+ public string TagName { get; set; }
+
+ public GetTopTracksCommand(ILastAuth auth, string tagName) : base(auth)
+ {
+ TagName = tagName;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("tag", TagName);
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var resultsToken = jtoken.SelectToken("toptracks");
+ var itemsToken = resultsToken.SelectToken("track");
+
+ return PageResponse.CreateSuccessResponse(itemsToken, resultsToken, LastTrack.ParseJToken, LastPageResultsType.Attr);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/IF.Lastfm.Core/Api/Commands/Track/AddShoutCommand.cs b/IF.Lastfm.Core/Api/Commands/Track/AddShoutCommand.cs
new file mode 100644
index 00000000..14b9ad76
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Track/AddShoutCommand.cs
@@ -0,0 +1,36 @@
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Helpers;
+
+namespace IF.Lastfm.Core.Api.Commands.Track
+{
+ [ApiMethodName("track.shout")]
+ internal class AddShoutCommand : PostAsyncCommandBase
+ {
+ public string Track { get; set; }
+
+ public string Artist { get; set; }
+
+ public string Message { get; set; }
+
+
+ public AddShoutCommand(ILastAuth auth, string track, string artist, string message) : base(auth)
+ {
+ Track = track;
+ Artist = artist;
+ Message = message;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("track", Track);
+ Parameters.Add("artist", Artist);
+ Parameters.Add("message", Message);
+ }
+
+ public override async Task HandleResponse(HttpResponseMessage response)
+ {
+ return await LastResponse.HandleResponse(response);
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Track/GetInfoCommand.cs b/IF.Lastfm.Core/Api/Commands/Track/GetInfoCommand.cs
new file mode 100644
index 00000000..cf86f941
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Track/GetInfoCommand.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Track
+{
+ [ApiMethodName("track.getInfo")]
+ internal class GetInfoCommand : GetAsyncCommandBase>
+ {
+ public string TrackMbid { get; set; }
+
+ public string TrackName { get; set; }
+
+ public string ArtistName { get; set; }
+
+ public string Username { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetInfoCommand(ILastAuth auth) : base(auth) { }
+
+ public override void SetParameters()
+ {
+ if (TrackMbid != null)
+ {
+ Parameters.Add("mbid", TrackMbid);
+ }
+ else
+ {
+ Parameters.Add("track", TrackName);
+ Parameters.Add("artist", ArtistName);
+ }
+
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ if (!string.IsNullOrWhiteSpace(Username))
+ {
+ Parameters.Add("username", Username);
+ }
+ }
+
+ public override async Task> HandleResponse(HttpResponseMessage response)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+
+ LastResponseStatus status;
+ if (LastFm.IsResponseValid(json, out status) && response.IsSuccessStatusCode)
+ {
+ var jtoken = JsonConvert.DeserializeObject(json);
+ var track = LastTrack.ParseJToken(jtoken.SelectToken("track"));
+
+ return LastResponse.CreateSuccessResponse(track);
+ }
+ else
+ {
+ return LastResponse.CreateErrorResponse>(status);
+ }
+ }
+ }
+}
diff --git a/IF.Lastfm.Core/Api/Commands/Track/GetShoutsCommand.cs b/IF.Lastfm.Core/Api/Commands/Track/GetShoutsCommand.cs
new file mode 100644
index 00000000..2bd560fb
--- /dev/null
+++ b/IF.Lastfm.Core/Api/Commands/Track/GetShoutsCommand.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using IF.Lastfm.Core.Api.Enums;
+using IF.Lastfm.Core.Api.Helpers;
+using IF.Lastfm.Core.Objects;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace IF.Lastfm.Core.Api.Commands.Track
+{
+ [ApiMethodName("track.getShouts")]
+ internal class GetShoutsCommand : GetAsyncCommandBase>
+ {
+ public string TrackName { get; set; }
+
+ public string ArtistName { get; set; }
+
+ public bool Autocorrect { get; set; }
+
+ public GetShoutsCommand(ILastAuth auth, string trackname, string artistname)
+ : base(auth)
+ {
+ TrackName = trackname;
+ ArtistName = artistname;
+ }
+
+ public override void SetParameters()
+ {
+ Parameters.Add("track", TrackName);
+ Parameters.Add("artist", ArtistName);
+ Parameters.Add("autocorrect", Convert.ToInt32(Autocorrect).ToString());
+
+ AddPagingParameters();
+ DisableCaching();
+ }
+
+ public override async Task