Compare commits

...

11 Commits

Author SHA1 Message Date
Maff f688cd63a9 1.8: Make Nightwave for Workgroups/Nightwave.NET work again
Nightwave Plaza had a couple of API changes! i have now made it work.
2023-05-18 20:05:30 +00:00
Maff 6be173855a Add WISE InstallMaster 8 install/upgrade/uninstall file generator project. 2019-11-08 13:54:05 +00:00
Maff 877c221bd3 Update version number in project. 2019-11-08 13:49:01 +00:00
Maff b866bdbb08 Minor UI glitch fixed; artist name was being truncated due to not having been configured to span both columns. This commit marks release 1.7-0 rev.1 2019-11-08 13:24:28 +00:00
Maff 68b3909539 Update readme to link to website; this commit marks release 1.7-0r0 2019-11-07 15:42:33 +00:00
Maff e34145b6b0 Remove unused resources, add license file to libplaza, update general structure of license file, update readme, add building/releasing/signing details, make Nightwave.NET for Workgroups support 32-bit and 64-bit properly. 2019-11-07 15:40:25 +00:00
Maff fbad2e0ce4 🦀 WinPlaza is gone 🦀
ported WInPlaza to .Net Core 3 which brings with it the tiniest amount of bugfixes.. also made the main window resizable. for now, i'm dropping ClickOnce deployment (don't think .Net Core supports it anyway)
2019-11-07 14:09:02 +00:00
Maff 73914ad8a0 Remove now-obsolete testplaza WinForms app project (may revisit in future as I do intend on a .Net Core-compatible GUI app). Add inline documentation for the libplaza library project. Fix some bugs with the WinPlaza GUI app. 2019-11-05 16:45:22 +00:00
Maff 104901142e some more improvements to vote handling. buttons now reflect what they will do! 2019-11-04 17:05:10 +00:00
Maff e691687ee1 fix an API change (status[playing][updated] changed to status[updated_at]), add ability to see your vote on the currently playing track (thanks mr.plaza!). finally, only allow like/dislike buttons to be clickable if user is logged in. 2019-11-03 23:44:35 +00:00
Maff a8dcd3a150 changes to how logins are handled, actually respect the "don't save my credentials" box, you can no longer double click the like or dislike button in order to do the same thing twice 2019-11-01 17:02:26 +00:00
54 changed files with 2906 additions and 1311 deletions

View File

@ -93,7 +93,7 @@ csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
# Code-block preferences
csharp_prefer_braces = when_multiline:suggestion
csharp_prefer_braces = false:suggestion
csharp_prefer_simple_using_statement = true:suggestion
# Expression-level preferences

View File

@ -0,0 +1,6 @@
<Application x:Class="NightwaveForWorkgroups.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NightwaveForWorkgroups"
StartupUri="MainWindow.xaml">
</Application>

View File

@ -6,7 +6,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace WinPlaza {
namespace NightwaveForWorkgroups {
/// <summary>
/// Interaction logic for App.xaml
/// </summary>

View File

@ -0,0 +1,18 @@
# Building Nightwave.NET for Workgroups
In theory, you can build Nightwave.NET by simply loading the solution
in Visual Studio 2019 or newer, allowing the nuget packages
to restore, and then cleaning and building the solution. This
hasn't been thoroughly tested.
# Releasing Nightwave.NET for Workgroups
For a release of Nightwave.NET, as it is now based on .NET Core 3,
you must ensure the package contains the following files, in
addition to any generated EXE and DLL files (incl. in subfolders):
* _ProgramExeName_.runtimeconfig.json
Without this, the application will silently fail to open.
## Signing a release
TBD because I can't afford to (and cannot be bothered to) get
an actual code signing key from a standard trusted authority.

View File

@ -1,7 +1,7 @@
WinPlaza and libplaza are licensed under the BSD-3-Clause license provided below:
# Nightwave.NET for Workgroups & libplaza license
Copyright 2019, [Matthew Connelly][1]
## BSD-3-Clause
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
@ -14,12 +14,12 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
[1]: https://maff.scot
Third-Party licenses are included below.
# Third-Party Licenses & Acknowledgements
#ManagedBass
## ManagedBass
Copyright (c) 2016 [Mathew Sachin](https://github.com/MathewSachin)
## The MIT License
### The MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the Software without restriction,
@ -35,11 +35,12 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#Newtonsoft JSON.Net
The MIT License (MIT)
## Newtonsoft JSON.Net
Copyright (c) 2007 James Newton-King
### The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
@ -57,11 +58,11 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#Meziantou.Framework, Meziantou.Framework.Win32.CredentialManager
MIT License
## Meziantou.Framework, Meziantou.Framework.Win32.CredentialManager
Copyright (c) 2019 Gérald Barré
### MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
@ -80,9 +81,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
#Bass
Bass is commercial software and is used in this application within the bounds of the licensing specifications
listed on the un4seen website. The copyright notice from its headers is listed below in lieu of an actual
license file, as none could be found.
## Bass
Copyright (c) 1999-2019 Un4seen Developments Ltd.
Copyright (c) 1999-2019 Un4seen Developments Ltd.
Bass is commercial software and is used in this application
within the bounds of the licensing specifications listed on
the un4seen website.
The copyright notice from its headers is included above
n lieu of an actual license file, as none could be found.

View File

@ -0,0 +1,90 @@
<Window x:Class="NightwaveForWorkgroups.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:NightwaveForWorkgroups"
mc:Ignorable="d"
Title="Nightwave.Net" Height="210" Width="480" MinHeight="210" MinWidth="480" MaxHeight="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" Background="#FFF0F0F0" Deactivated="Window_Deactivated" Activated="Window_Activated" UseLayoutRounding="True" Closing="Window_Closing">
<Window.TaskbarItemInfo>
<TaskbarItemInfo>
<TaskbarItemInfo.ThumbButtonInfos>
<ThumbButtonInfo x:Name="tbPlayPause" Description="Play/Stop" Click="TbPlayPause_Click" ImageSource="Resources/playstop.png"/>
<ThumbButtonInfo x:Name="tbMute" Description="Mute/Unmute" Click="TbMute_Click" ImageSource="Resources/unmuted.png"/>
</TaskbarItemInfo.ThumbButtonInfos>
</TaskbarItemInfo>
</Window.TaskbarItemInfo>
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Parent.Height, RelativeSource={RelativeSource Self}}" MinWidth="148"/>
<ColumnDefinition Width="163*"/>
<ColumnDefinition Width="163*"/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<Image x:Name="art" Margin="0,0,0,0" Grid.Column="0" Stretch="Uniform"/>
<Label x:Name="lbTitle" Content="Track title" HorizontalAlignment="Left" Margin="2,6,0,0" VerticalAlignment="Top" FontSize="12" FontWeight="Bold" Grid.Column="1" Grid.ColumnSpan="2"/>
<Label x:Name="lbArtist" Content="Track artist" HorizontalAlignment="Left" Margin="2,28,0,0" VerticalAlignment="Top" FontSize="11" Grid.Column="1" Grid.ColumnSpan="2"/>
<Label x:Name="lbAlbum" Content="Track album" HorizontalAlignment="Left" Margin="2,45,0,0" VerticalAlignment="Top" FontStyle="Italic" FontSize="10" Grid.Column="1" Grid.ColumnSpan="2"/>
<Slider x:Name="slDuration" Margin="0,87,0,0" VerticalAlignment="Top" IsEnabled="False" Grid.ColumnSpan="2" Height="18" Grid.Column="1" AutoToolTipPlacement="TopLeft"/>
<Label x:Name="lbElapsed" Content="00:00" HorizontalAlignment="Left" Margin="0,99,0,0" VerticalAlignment="Top" FontStyle="Italic" FontSize="9" Grid.Column="1"/>
<Label x:Name="lbTime" Content="00:00" Margin="0,99,0,0" VerticalAlignment="Top" HorizontalAlignment="Right" FontStyle="Italic" FontSize="9" Grid.Column="2"/>
<Button x:Name="btLike" Content="❤" ToolTip="Like this track" IsEnabled="False" HorizontalAlignment="Right" Margin="0,40,2,0" Width="22" Height="20" VerticalAlignment="Top" FontSize="10" Grid.Column="2" Click="BtLike_Click"/>
<!--<Button x:Name="btDislike" Content="💔" ToolTip="Dislike this track" IsEnabled="False" HorizontalAlignment="Right" Margin="0,64,2,0" Width="22" Height="20" VerticalAlignment="Top" FontSize="10" Grid.Column="2" Click="BtDislike_Click"/>-->
<Label x:Name="lbLikeCt" Content="0" HorizontalAlignment="Right" Margin="0,36.5,22,0" VerticalAlignment="Top" Grid.Column="2" Grid.ColumnSpan="2"/>
<!--<Label x:Name="lbDislikeCt" Content="0" HorizontalAlignment="Right" Margin="0,60.5,22,0" VerticalAlignment="Top" Grid.Column="2" Grid.ColumnSpan="2"/>-->
<Image x:Name="imLogin" IsHitTestVisible="False" Panel.ZIndex="1" Width="18" Height="18" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,17,3,0" Grid.Column="2" Source="Resources/usr.png" Stretch="UniformToFill"/>
<Button x:Name="btLogin" Content="" ToolTip="Log into Nightwave Plaza" HorizontalAlignment="Right" Margin="0,16,2,0" Width="22" Height="20" VerticalAlignment="Top" FontSize="9" Grid.Column="2" Click="BtLogin_Click"/>
<TextBlock ToolTip="Visit Nightwave Plaza! If you don't have an account, you can make one there!" FontSize="10" FontStyle="Italic" Grid.Column="2" Grid.Row="0" Margin="0,0,2,13" HorizontalAlignment="Right" VerticalAlignment="Bottom" Foreground="#FF5A5A5A">
<Hyperlink NavigateUri="https://plaza.one" RequestNavigate="Hyperlink_RequestNavigate" Foreground="{Binding Parent.Foreground, RelativeSource={RelativeSource Self}}" TextDecorations="{x:Null}" Cursor="ArrowCD" TargetName="Nightwave Plaza">plaza.one</Hyperlink>
</TextBlock>
<DockPanel Grid.ColumnSpan="5" Margin="0,0,0,0" Grid.Row="1">
<StatusBar x:Name="statusBar" Margin="0,0,0,-2" DockPanel.Dock="Bottom" VerticalAlignment="Bottom" BorderBrush="#FFDFDFDF" BorderThickness="1" Height="28" VerticalContentAlignment="Bottom">
<StatusBarItem HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="btPlayPause" Content="⏯" ToolTip="Start/Stop playback" VerticalContentAlignment="Center" Checked="BtPlayPause_Click" Unchecked="BtPlayPause_Click" Margin="0,-2,4,-2"/>
<TextBlock x:Name="sbListeners" Text="Listener count" Margin="2,0,0,0"/>
</StackPanel>
</StatusBarItem>
<Separator/>
<StatusBarItem HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="sbStatus" Text="Status"/>
</StackPanel>
</StatusBarItem>
<StatusBarItem DockPanel.Dock="Right" HorizontalAlignment="Right">
<Slider x:Name="sbVol" Maximum="100" Minimum="0" ToolTip="Volume level" Value="100" SmallChange="1" LargeChange="5" Width="80" ValueChanged="SbVol_ValueChanged" MouseWheel="SbVol_MouseWheel" Margin="0,0,4,0"/>
</StatusBarItem>
<Separator DockPanel.Dock="Right" HorizontalAlignment="Right" />
<StatusBarItem DockPanel.Dock="Right" HorizontalAlignment="Right" >
<StackPanel Orientation="Horizontal">
<!--TODO implement tracking of time spent listening (this session and all-time)
<TextBlock x:Name="sbListeningTimers" Text="00:00 (00:00)"/>-->
<TextBlock x:Name="sbVote" Text="Vote" Margin="0,0,4,0"/>
<ToggleButton x:Name="sbOnTop" Content="📌" ToolTip="Toggle always-on-top" Checked="SbOnTop_Click" Unchecked="SbOnTop_Click" Margin="0,-2,4,-2"/>
<ToggleButton x:Name="sbMute" Content="🔊" ToolTip="Toggle mute" Checked="SbMute_Click" Unchecked="SbMute_Click" Margin="0,-2,0,-2"/>
</StackPanel>
</StatusBarItem>
</StatusBar>
</DockPanel>
<Rectangle Height="1" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="0" Margin="0,0,0,10" Fill="#FF003C49" VerticalAlignment="Bottom"/>
<Rectangle Width="1" Grid.Column="0" Grid.Row="0" Margin="0,0,0,0" Fill="#FFBFBFBF" HorizontalAlignment="Right"/>
<Canvas x:Name="chLeftCvs" Margin="0,0,0,0" Height="10" VerticalAlignment="Bottom" Background="#FFDDDDDD" RenderTransformOrigin="0.5,0.5" Grid.Column="1">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="1" ScaleX="-1"/>
<SkewTransform AngleY="0" AngleX="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="chLeft" Width="0" Height="10" HorizontalAlignment="Left" Fill="#FF003C49"/>
</Canvas>
<Canvas x:Name="chRightCvs" Margin="0,0,0,0" Height="10" VerticalAlignment="Bottom" Background="#FFDDDDDD" Grid.Column="2">
<Rectangle x:Name="chRight" Width="0" Height="10" Fill="#FF003C49"/>
</Canvas>
</Grid>
</Window>

View File

@ -8,70 +8,126 @@ using libplaza;
using ManagedBass;
using Meziantou.Framework.Win32;
namespace WinPlaza {
namespace NightwaveForWorkgroups {
public partial class MainWindow : Window {
#region DLL Imports
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName);
#endregion
#region StateVars
static readonly object BufferLock=new object();
private static readonly object BufferLock=new object();
private int BuffCh;
private int BufferReqs;
private int MuteVol=-1;
private int GfxWidthStep=-1;
private string LastArtwork="";
private Nightwave plaza=new Nightwave();
private readonly Nightwave NwP=new Nightwave();
private Timer tRefresh;
private Timer tAudio;
private Timer tGfx;
private Timer tLogon;
private bool AudioInit=false;
private bool LikedCTrack=false;
#endregion
#region Delegates
private delegate void dgtNoParam();
private delegate void dgtBool(bool b);
private delegate void dgtStr(string s);
private delegate void dgtCtrl(System.Windows.Controls.Control c);
#endregion
#region CrossThreadCalls
private void Leveller() {
if(GfxWidthStep<0)
if(GfxWidthStep < 0)
GfxWidthStep = 32768 / (int)chLeftCvs.ActualWidth;
chLeft.Width = Bass.ChannelGetLevelLeft(BuffCh) / GfxWidthStep;
chRight.Width = Bass.ChannelGetLevelRight(BuffCh) / GfxWidthStep;
}
private void DgtToggleEnabled(System.Windows.Controls.Control c) =>
c.IsEnabled = !c.IsEnabled;
private void DgtStatus(string s) =>
sbStatus.Text = s;
private void DgtTitle(string s) =>
Title = s;
private void SetStatus(string msg) =>
Dispatcher.BeginInvoke(new dgtStr(_Status), msg);
Dispatcher.BeginInvoke(new dgtStr(DgtStatus), msg);
private void SetTitle(string msg) =>
Dispatcher.BeginInvoke(new dgtStr(_Title), msg);
Dispatcher.BeginInvoke(new dgtStr(DgtTitle), msg);
private async void DgtStartupLogon() {
Credential c=CredentialManager.ReadCredential("Nightwave.NET");
if(c is null)
return;
if(await plaza.Login(c.UserName, c.Password)!=true)
try {
if(await NwP.Login(c.UserName, c.Password) != true)
return;
} catch(Exception) {
return;
}
btLogin.IsEnabled = false;
btLogin.Visibility = Visibility.Hidden;
imLogin.IsEnabled = false;
imLogin.Visibility = Visibility.Hidden;
SetStatus($"Logged in as {await plaza.GetUser()}");
//btDislike.IsEnabled = true;
btLike.IsEnabled = true;
SetStatus($"Logged in as {await NwP.GetUser()}");
}
private async void DgtRefresh(bool Force = false) {
Status s=await plaza.Broadcast(Force);
if(s.FaultOccurred||s.InMaintenance) {
GfxWidthStep = 32768 / (int)chLeftCvs.ActualWidth;
Status s;
try {
s = await NwP.Status(Force);
} catch(NullReferenceException) {
SetStatus("Ran into a problem while fetching now-playing.");
return;
}
if(s.FaultOccurred || s.InMaintenance) {
if(s.InMaintenance)
SetStatus("Nightwave Plaza is currently in maintenance mode.");
else
SetStatus("Ran into a problem while fetching now-playing.");
return;
}
if(s.ArtworkUri == null || s.Duration == 0) {
Console.WriteLine("why is async so hard");
throw new Exception("Something went real weird, woah. Time to Crash!");
}
slDuration.Value = s.CalculatedElapsed;
sbListeners.Text = $"{s.Listeners} listeners";
lbLikeCt.Content = s.Likes;
lbDislikeCt.Content = s.Dislikes;
//lbDislikeCt.Content = s.Dislikes;
lbElapsed.Content = $"{(s.CalculatedElapsed / 60).ToString("D")}:{(s.CalculatedElapsed % 60).ToString("D2")}";
if(LastArtwork != s.ArtworkUri || Force) {
//TODO should probably break this part out into a separate function cause this feels real messy
Vote v = await NwP.Vote() ?? Vote.Neutral;
switch(v) {
/*case Vote.Dislike:
sbVote.Text = "👎";
sbVote.ToolTip = "You dislike this.";
btLike.Content = "👍";
btLike.ToolTip = "Like this track";
btDislike.Content = "🤷‍♀️";
btDislike.ToolTip = "Remove your vote for this track";
break;*/
case Vote.Like:
sbVote.Text = "👍";
sbVote.ToolTip = "You like this.";
btLike.Content = "💖";
btLike.ToolTip = "Favourite this track";
//btDislike.Content = "👎";
//btDislike.ToolTip = "Dislike this track";
break;
case Vote.Favourite:
sbVote.Text = "💖";
sbVote.ToolTip = "You have favourited this.";
btLike.Content = "🤷‍♀️";
btLike.ToolTip = "Remove your vote for this track";
//btDislike.Content = "👎";
//btDislike.ToolTip = "Dislike this track";
break;
default:
sbVote.Text = "🤷‍♀️";
sbVote.ToolTip = "You have no feelings towards this, or you have not expressed your feelings for this.";
btLike.Content = "👍";
btLike.ToolTip = "Like this track";
//btDislike.Content = "👎";
//btDislike.ToolTip = "Dislike this track";
break;
}
//TODO visual effect on the like/dislike buttons to indicate user's vote
lbArtist.Content = s.Artist;
lbTitle.Content = s.Title;
lbAlbum.Content = s.Album;
@ -80,12 +136,10 @@ namespace WinPlaza {
slDuration.Maximum = s.Duration;
lbTime.Content = $"{(s.Duration / 60).ToString("D")}:{(s.Duration % 60).ToString("D2")}";
if(LastArtwork != s.ArtworkUri) {
if(btPlayPause.IsChecked == true)
SetStatus("Playing");
LikedCTrack = false;
art.Source = new BitmapImage(new Uri(s.ArtworkUri));
LastArtwork = s.ArtworkUri;
}
SetStatus("");
}
}
private void DgtAudio() {
@ -125,7 +179,8 @@ namespace WinPlaza {
else
tAudio.Start();
});
private void PlazaLoad() => LoadUri("https://radio.plaza.one/ogg");
private void PlazaLoad() =>
LoadUri("https://radio.plaza.one/ogg");
private void PlazaStop() {
tGfx.Stop();
lock(BufferLock) {
@ -161,22 +216,23 @@ namespace WinPlaza {
private void BtPlayPause_Click(object sender, RoutedEventArgs e) {
if(!AudioInit)
return;
if(btPlayPause.IsChecked == true)
_ = Dispatcher.BeginInvoke(new dgtNoParam(PlazaLoad));
else
_ = Dispatcher.BeginInvoke(new dgtNoParam(PlazaStop));
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btPlayPause);
_ = btPlayPause.IsChecked == true
? Dispatcher.BeginInvoke(new dgtNoParam(PlazaLoad))
: Dispatcher.BeginInvoke(new dgtNoParam(PlazaStop));
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btPlayPause);
}
private void SbMute_Click(object sender, RoutedEventArgs e) {
if(MuteVol<0) {
if(MuteVol < 0) {
MuteVol = Bass.GlobalStreamVolume;
Bass.GlobalStreamVolume = 0;
sbMute.Content = "🔇";
tbMute.ImageSource = (BitmapImage)FindResource("tbMuted");
tbMute.ImageSource = new BitmapImage(new Uri("pack://application:,,,/;component/Resources/muted.png"));
} else {
Bass.GlobalStreamVolume = MuteVol;
MuteVol = -1;
sbMute.Content = "🔊";
tbMute.ImageSource = (BitmapImage)FindResource("tbAudible");
tbMute.ImageSource = new BitmapImage(new Uri("pack://application:,,,/;component/Resources/unmuted.png"));
}
}
private void SbVol_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e) {
@ -184,50 +240,86 @@ namespace WinPlaza {
e.Handled = true;
}
private void SbVol_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
if(MuteVol >= 0)
if(!AudioInit)
return;
if(MuteVol >= 0 && sbMute.IsChecked == true)
sbMute.IsChecked = false;
Bass.GlobalStreamVolume = (int)sbVol.Value * 100;
}
private async void BtLogin_Click(object sender, RoutedEventArgs e) {
CredentialResult _c=CredentialManager.PromptForCredentials(messageText:"Please log into your Nightwave Plaza account", captionText:"Log into plaza.one");
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btLogin);
CredentialResult _c=CredentialManager.PromptForCredentials(
messageText:"Please log into your Nightwave Plaza account",
captionText:"Log into plaza.one");
if(_c is null)
return;
CredentialManager.WriteCredential("Nightwave.NET", _c.UserName, _c.Password, CredentialPersistence.Enterprise);
Credential c=CredentialManager.ReadCredential("Nightwave.NET");
if(c is null)
throw new Exception("what the fuck");
Console.WriteLine($"did login work? {await plaza.Login(c.UserName, c.Password)}");
Console.WriteLine($"can validate? {await plaza.CheckSession()}");
#if DEBUG
Console.WriteLine($"token is {plaza.GetAuthorizationToken()}");
#endif
if(!await NwP.Login(_c.UserName, _c.Password)) {
SetStatus("Login failed! Please try again.");
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btLogin);
return;
}
//user may have stored credentials and not want to keep them any more, delete with reckless abandon.
// on certain versions of windows, the lack of existing creds here will cause an exception.
try {
CredentialManager.DeleteCredential("Nightwave.NET");
} catch(Exception) { }
if(_c.CredentialSaved == CredentialSaveOption.Selected) {
CredentialManager.WriteCredential("Nightwave.NET", _c.UserName, _c.Password, CredentialPersistence.Enterprise);
Credential c=CredentialManager.ReadCredential("Nightwave.NET");
if(c is null) {
SetStatus("Problem saving to Windows Credential store. Sorry.");
throw new Exception("what the fuck");
}
}
if(!await NwP.CheckSession()) {
SetStatus("Something went wrong during login! Sorry.");
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btLogin);
return;
}
//#endif
btLogin.IsEnabled = false;
btLogin.Visibility = Visibility.Hidden;
imLogin.IsEnabled = false;
imLogin.Visibility = Visibility.Hidden;
//btDislike.IsEnabled = true;
btLike.IsEnabled = true;
SetStatus($"Logged in as {await NwP.GetUser()}");
}
private async void BtLike_Click(object sender, RoutedEventArgs e) {
Nightwave.Vote v = Nightwave.Vote.Like;
if(LikedCTrack)
v = Nightwave.Vote.Favourite;
if(await plaza.CastVote(v)) {
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btLike);
Vote v = await NwP.Vote() ?? Vote.Neutral;
// Kinda like a garbage state machine
v = v switch {
Vote.Like => Vote.Favourite,
Vote.Favourite => Vote.Neutral,
_ => Vote.Like,
};
if(await NwP.Vote(v)) {
SetStatus($"Submitted {v}!");
LikedCTrack = !LikedCTrack; //This should allow "un-favouriting"
} else
SetStatus($"Something went weird while casting {v}");
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btLike);
_ = Dispatcher.BeginInvoke(new dgtBool(DgtRefresh), true);
}
private async void BtDislike_Click(object sender, RoutedEventArgs e) {
if(await plaza.CastVote(Nightwave.Vote.Dislike))
SetStatus("Song disliked!");
/*private async void BtDislike_Click(object sender, RoutedEventArgs e) {
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btDislike);
Vote v = Vote.Dislike;
if(await NwP.Vote() == Vote.Dislike)
v = Vote.Neutral;
if(await NwP.Vote(v))
SetStatus($"Submitted {v}!");
else
SetStatus("Something went weird while disliking");
LikedCTrack = false;
SetStatus($"Something went weird while casting {v}");
_ = Dispatcher.BeginInvoke(new dgtCtrl(DgtToggleEnabled), btDislike);
_ = Dispatcher.BeginInvoke(new dgtBool(DgtRefresh), true);
}
}*/
private void SbOnTop_Click(object sender, RoutedEventArgs e) {
Topmost = sbOnTop.IsChecked == true;
if(tGfx.Enabled)
Topmost = sbOnTop.IsChecked.GetValueOrDefault(false);
WindowStyle = Topmost ? WindowStyle.None : WindowStyle.SingleBorderWindow;
ResizeMode = Topmost ? ResizeMode.NoResize : ResizeMode.CanResize;
//I don't remember what this is and I don't remember why it's being set
// only when always-on-top is enabled and then never again.
if(tGfx.Enabled && Topmost)
tGfx.Interval = 1000.0 / 60.0;
}
private void TbPlayPause_Click(object sender, EventArgs e) =>
@ -258,16 +350,33 @@ namespace WinPlaza {
#region GUIMethods
public MainWindow() {
InitializeComponent();
// TODO work out if this can be made less gross
// BASS library must explicitly be named "bass.dll" in order to be loaded (seemingly..) so arch-dependent directory is needed.
if(Environment.Is64BitProcess) {
if(LoadLibrary("lib64/bass.dll") == IntPtr.Zero) {
int lasterror = Marshal.GetLastWin32Error();
System.ComponentModel.Win32Exception innerEx = new System.ComponentModel.Win32Exception(lasterror);
innerEx.Data.Add("LastWin32Error", lasterror);
throw new Exception("can't load DLL", innerEx);
}
} else {
if(LoadLibrary("lib32/bass.dll") == IntPtr.Zero) {
int lasterror = Marshal.GetLastWin32Error();
System.ComponentModel.Win32Exception innerEx = new System.ComponentModel.Win32Exception(lasterror);
innerEx.Data.Add("LastWin32Error", lasterror);
throw new Exception("can't load DLL", innerEx);
}
}
if(!Bass.Init())
sbStatus.Text = "Unable to initialise audio subsystem";
else
AudioInit = true;
tRefresh = new Timer(300) { AutoReset = true };
tRefresh.Elapsed += EvtRefresh;
tRefresh.Start();
tLogon = new Timer(1000) { AutoReset = false };
tLogon.Elapsed += EvtLogon;
tLogon.Start();
if(!Bass.Init())
sbStatus.Text = "Unable to initialise audio subsystem";
else
AudioInit = true;
if(!AudioInit)
return;
tAudio = new Timer(50);
@ -277,13 +386,11 @@ namespace WinPlaza {
Bass.NetPlaylist = 1;
Bass.NetPreBuffer = 0;
}
~MainWindow() {
NwP.Dispose();
if(AudioInit)
_ = Bass.Free();
}
private void _Status(string s) => sbStatus.Text = s;
private void _Title(string s) => Title = s;
#endregion
}
}

View File

@ -0,0 +1,80 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Product>Nightwave.NET for Workgroups</Product>
<Company>Softpup Aesthetics &amp; Petting, p.r.p.l.</Company>
<Authors>Maff</Authors>
<AssemblyVersion>1.8.0.0</AssemblyVersion>
<FileVersion>1.8.0.0</FileVersion>
<NeutralLanguage>en-GB</NeutralLanguage>
<PackageProjectUrl>https://soft.pup.cloud/Nightwave.NET/</PackageProjectUrl>
<RepositoryUrl>https://commit.pup.cloud/maff/PlazaSharp/src/branch/master/NightwaveForWorkgroups</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>BSD-3-CLAUSE</PackageLicenseExpression>
<Version>1.8-0r0</Version>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Resources\app.ico</ApplicationIcon>
<StartupObject>NightwaveForWorkgroups.App</StartupObject>
<Description>An unofficial but lovingly-crafted digital tuner for the premier cyberspace radio station from the alternate future we wish we had, Nightwave Plaza.</Description>
<Copyright>Nightwave Plaza is wholly copyright and owned by its creator, I just wrote this unofficial app that talks to it.</Copyright>
<AssemblyName>NightwaveDotNET</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<None Remove="bass-x64.dll" />
<None Remove="Resources\app.ico" />
<None Remove="Resources\bass-x86.dll" />
<None Remove="Resources\muted.png" />
<None Remove="Resources\playstop.png" />
<None Remove="Resources\unmuted.png" />
<None Remove="Resources\usr.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="lib64\bass.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\app.ico" />
<EmbeddedResource Include="lib32\bass.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="ManagedBass" Version="2.0.4" />
<PackageReference Include="Meziantou.Framework.Win32.CredentialManager" Version="1.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libplaza\libplaza.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\muted.png" />
<Resource Include="Resources\playstop.png" />
<Resource Include="Resources\unmuted.png" />
<Resource Include="Resources\usr.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
# Nightwave.NET for Workgroups
An unofficial client for [Nightwave Plaza][1], which aims to
implement as much functionality of the site as possible
while maintaining aesthetics.
You can [get it here][6].
Intended future improvements include:
* Multiple visualiser types
* Better use of additional application real-estate when resized
* Integration with the Windows native media APIs (so keyboard play/pause buttons work)
* Storage for window height/width, always-on-top and volume state
* Log-out button lmfao
* Miniplayer!
## First-Party
Nightwave.NET uses **libplaza**, a basic .Net interface for
the Nightwave Plaza API. **libplaza** can be found in the same
Git repository as **Nightwave.NET for Workgroups**.
## Third-Party
Nightwave.NET uses the following third-party libraries and resources:
* [BASS][2] by Un4Seen Developments, via .Net wrapper library [ManagedBass][3] by Mathew Sachin, for audio streaming and playback functionality.
* [JSON.Net][4] by Newtonsoft for deserialisation of JSON strings.
* [CredentialManager][5] by Meziantou for access to the Windows Credential Manager APIs
## Privacy, data collection & usage
Nightwave.NET collects the following information and stores it locally in your Windows install's Enterprise Credentials Store:
* Username for Nightwave Plaza
* Password for Nightwave Plaza (encrypted)
Nightwave.NET does not transmit any data over the internet itself, however the Nightwave Plaza library interface, **libplaza**, uploads the following information to remote servers:
* Username and Password for Nightwave Plaza, uploaded by means of the Nightwave Plaza standard login mechanism, to **api.plaza.one/user/auth**, the standard login endpoint for Nightwave Plaza
* Your vote for the current track, whenever you cast it, uploaded by means of the Nightwave Plaza reactions mechanism, to **api.plaza.one/reactions**, the standard voting endpoint for Nightwave Plaza
Nightwave.NET and libplaza are both developed solely by me, and do not collect any information not mentioned here. To the best of my knowledge and personal verification,
no third-party library in use by either Nightwave.NET or libplaza collects any data or makes any network connections, with the exception of **BASS**, which
makes one network connection: to stream the Nightwave Radio audio stream to your computer.
[1]: https://plaza.one
[2]: https://www.un4seen.com/bass.html
[3]: https://github.com/ManagedBass/ManagedBass
[4]: https://github.com/JamesNK/Newtonsoft.Json
[5]: https://github.com/meziantou/Meziantou.Framework
[6]: https://soft.pup.cloud/Nightwave.NET/

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 711 B

After

Width:  |  Height:  |  Size: 711 B

View File

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 228 B

View File

Before

Width:  |  Height:  |  Size: 527 B

After

Width:  |  Height:  |  Size: 527 B

View File

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 396 B

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.6.3.0" name="NightwaveDotNet.app" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
<applicationRequestMinimum>
<PermissionSet Unrestricted="true" ID="Custom" SameSite="site" />
<defaultAssemblyRequest permissionSetReference="Custom" />
</applicationRequestMinimum>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>

Binary file not shown.

View File

@ -3,14 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29306.81
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinPlaza", "WinPlaza\WinPlaza.csproj", "{72EEB593-D74D-4775-875A-CD0078923EC3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libplaza", "libplaza\libplaza.csproj", "{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testplaza", "testplaza\testplaza.csproj", "{DAB02D69-CD02-4C74-A110-7907E5B4A568}"
ProjectSection(ProjectDependencies) = postProject
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3} = {B8CD29C0-4FAF-4A02-B00C-3180E60841D3}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libplaza", "libplaza\libplaza.csproj", "{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NightwaveForWorkgroups", "NightwaveForWorkgroups\NightwaveForWorkgroups.csproj", "{65CB13F7-C784-49F9-9365-B227D8B9D4CA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -18,18 +13,14 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{72EEB593-D74D-4775-875A-CD0078923EC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72EEB593-D74D-4775-875A-CD0078923EC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72EEB593-D74D-4775-875A-CD0078923EC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72EEB593-D74D-4775-875A-CD0078923EC3}.Release|Any CPU.Build.0 = Release|Any CPU
{DAB02D69-CD02-4C74-A110-7907E5B4A568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAB02D69-CD02-4C74-A110-7907E5B4A568}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAB02D69-CD02-4C74-A110-7907E5B4A568}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAB02D69-CD02-4C74-A110-7907E5B4A568}.Release|Any CPU.Build.0 = Release|Any CPU
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}.Release|Any CPU.Build.0 = Release|Any CPU
{65CB13F7-C784-49F9-9365-B227D8B9D4CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65CB13F7-C784-49F9-9365-B227D8B9D4CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65CB13F7-C784-49F9-9365-B227D8B9D4CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65CB13F7-C784-49F9-9365-B227D8B9D4CA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View File

@ -1,14 +0,0 @@
<Application x:Class="WinPlaza.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WinPlaza"
StartupUri="MainWindow.xaml">
<Application.Resources>
<BitmapImage x:Key="tbPlaying" UriSource="/WinPlaza;component/Resources/music.png"/>
<BitmapImage x:Key="tbStopped" UriSource="/WinPlaza;component/Resources/nomusic.png"/>
<BitmapImage x:Key="tbPlayStop" UriSource="/WinPlaza;component/Resources/playstop.png"/>
<BitmapImage x:Key="tbMuted" UriSource="/WinPlaza;component/Resources/muted.png"/>
<BitmapImage x:Key="tbAudible" UriSource="/WinPlaza;component/Resources/unmuted.png"/>
<BitmapImage x:Key="imUsr" UriSource="/WinPlaza;component/Resources/usr.png"/>
</Application.Resources>
</Application>

View File

@ -1,82 +0,0 @@
<Window x:Class="WinPlaza.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WinPlaza"
mc:Ignorable="d"
Title="Nightwave.Net" Height="206" Width="480" Background="#FFF0F0F0" ResizeMode="CanMinimize" Deactivated="Window_Deactivated" Activated="Window_Activated" UseLayoutRounding="True" Closing="Window_Closing">
<Window.TaskbarItemInfo>
<TaskbarItemInfo>
<TaskbarItemInfo.ThumbButtonInfos>
<ThumbButtonInfo x:Name="tbPlayPause" Description="Play/Stop" Click="TbPlayPause_Click" ImageSource="{DynamicResource tbPlayStop}"/>
<ThumbButtonInfo x:Name="tbMute" Description="Mute/Unmute" Click="TbMute_Click" ImageSource="{DynamicResource tbAudible}"/>
</TaskbarItemInfo.ThumbButtonInfos>
</TaskbarItemInfo>
</Window.TaskbarItemInfo>
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="148*"/>
<ColumnDefinition Width="163*"/>
<ColumnDefinition Width="163*"/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<Image x:Name="art" Margin="0,0,0,0" Grid.Column="0" Stretch="UniformToFill"/>
<Label x:Name="lbTitle" Content="リサフランク420 / 現代のコンピュ" HorizontalAlignment="Left" Margin="2,6,0,0" VerticalAlignment="Top" FontSize="12" FontWeight="Bold" Grid.ColumnSpan="2" Grid.Column="1"/>
<Label x:Name="lbArtist" Content="MACプラス Macintosh Plus" HorizontalAlignment="Left" Margin="2,28,0,0" VerticalAlignment="Top" FontSize="11" Grid.Column="1"/>
<Label x:Name="lbAlbum" Content="フローラルの専門店 Floral Shoppe" HorizontalAlignment="Left" Margin="2,45,0,0" VerticalAlignment="Top" FontStyle="Italic" FontSize="10" Grid.Column="1" Grid.ColumnSpan="2"/>
<Slider x:Name="slDuration" Margin="0,87,0,0" VerticalAlignment="Top" IsEnabled="False" Grid.ColumnSpan="2" Height="18" Grid.Column="1" AutoToolTipPlacement="TopLeft"/>
<Label x:Name="lbElapsed" Content="00:00" HorizontalAlignment="Left" Margin="0,99,0,0" VerticalAlignment="Top" FontStyle="Italic" FontSize="9" Grid.Column="1"/>
<Label x:Name="lbTime" Content="00:00" Margin="0,99,0,0" VerticalAlignment="Top" HorizontalAlignment="Right" FontStyle="Italic" FontSize="9" Grid.Column="2"/>
<Button x:Name="btLike" Content="❤" ToolTip="Like this track" HorizontalAlignment="Right" Margin="0,40,2,0" Width="22" Height="20" VerticalAlignment="Top" FontSize="10" Grid.Column="2" Click="BtLike_Click"/>
<Button x:Name="btDislike" Content="💔" ToolTip="Dislike this track" HorizontalAlignment="Right" Margin="0,64,2,0" Width="22" Height="20" VerticalAlignment="Top" FontSize="10" Grid.Column="2" Click="BtDislike_Click"/>
<Label x:Name="lbLikeCt" Content="0" HorizontalAlignment="Right" Margin="0,36.5,22,0" VerticalAlignment="Top" Grid.Column="2" Grid.ColumnSpan="2"/>
<Label x:Name="lbDislikeCt" Content="0" HorizontalAlignment="Right" Margin="0,60.5,22,0" VerticalAlignment="Top" Grid.Column="2" Grid.ColumnSpan="2"/>
<Image x:Name="imLogin" Source="{DynamicResource imUsr}" IsHitTestVisible="False" Panel.ZIndex="1" Width="18" Height="18" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,17,3,0" Grid.Column="2"/>
<Button x:Name="btLogin" Content="" ToolTip="Log into Nightwave Plaza" HorizontalAlignment="Right" Margin="0,16,2,0" Width="22" Height="20" VerticalAlignment="Top" FontSize="9" Grid.Column="2" Click="BtLogin_Click"/>
<TextBlock ToolTip="Visit Nightwave Plaza! If you don't have an account, you can make one there!" FontSize="10" FontStyle="Italic" Grid.Column="2" Grid.Row="0" Margin="0,0,2,13" HorizontalAlignment="Right" VerticalAlignment="Bottom" Foreground="#FF5A5A5A">
<Hyperlink NavigateUri="https://plaza.one" RequestNavigate="Hyperlink_RequestNavigate" Foreground="{Binding Parent.Foreground, RelativeSource={RelativeSource Self}}" TextDecorations="{x:Null}" Cursor="ArrowCD" TargetName="Nightwave Plaza">plaza.one</Hyperlink>
</TextBlock>
<DockPanel Grid.ColumnSpan="5" Margin="0,0,0,0" Grid.Row="1">
<StatusBar x:Name="statusBar" Margin="0,0,0,-2" DockPanel.Dock="Bottom" VerticalAlignment="Bottom" BorderBrush="#FFDFDFDF" BorderThickness="1" Height="28" VerticalContentAlignment="Bottom">
<StatusBarItem HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="sbListeners" Text="420 listeners" Margin="4,0,4,0"/>
<ToggleButton x:Name="btPlayPause" Content="⏯" ToolTip="Start/Stop playback" VerticalContentAlignment="Center" Checked="BtPlayPause_Click" Unchecked="BtPlayPause_Click" Margin="0,-2,4,-2"/>
<TextBlock x:Name="sbStatus" Text="Idle.."/>
</StackPanel>
</StatusBarItem>
<StatusBarItem HorizontalAlignment="Right">
<StackPanel Orientation="Horizontal">
<!--TODO implement tracking of time spent listening (this session and all-time)
<TextBlock x:Name="sbListeningTimers" Text="00:00 (00:00)"/>-->
<ToggleButton x:Name="sbOnTop" Content="📌" ToolTip="Toggle always-on-top" Checked="SbOnTop_Click" Margin="0,-2,4,-2"/>
<ToggleButton x:Name="sbMute" Content="🔊" ToolTip="Toggle mute" Checked="SbMute_Click" Unchecked="SbMute_Click" Margin="0,-2,0,-2"/>
<Separator Width="6" Background="{x:Null}" Foreground="{x:Null}"/>
<Slider x:Name="sbVol" Maximum="100" Minimum="0" ToolTip="Volume level" Value="100" SmallChange="1" LargeChange="5" Width="80" ValueChanged="SbVol_ValueChanged" MouseWheel="SbVol_MouseWheel" Margin="0,0,4,0"/>
</StackPanel>
</StatusBarItem>
</StatusBar>
</DockPanel>
<Rectangle Height="1" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="0" Margin="0,0,0,10" Fill="#FF003C49" VerticalAlignment="Bottom"/>
<Rectangle Width="1" Grid.Column="0" Grid.Row="0" Margin="0,0,0,0" Fill="#FFBFBFBF" HorizontalAlignment="Right"/>
<Canvas x:Name="chLeftCvs" Margin="0,0,0,0" Height="10" VerticalAlignment="Bottom" Background="#FFDDDDDD" RenderTransformOrigin="0.5,0.5" Grid.Column="1">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="1" ScaleX="-1"/>
<SkewTransform AngleY="0" AngleX="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="chLeft" Width="0" Height="10" HorizontalAlignment="Left" Fill="#FF003C49"/>
</Canvas>
<Canvas x:Name="chRightCvs" Margin="0,0,0,0" Height="10" VerticalAlignment="Bottom" Background="#FFDDDDDD" Grid.Column="2">
<Rectangle x:Name="chRight" Width="0" Height="10" Fill="#FF003C49"/>
</Canvas>
</Grid>
</Window>

View File

@ -1,57 +0,0 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Nightwave.Net")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("pup.cloud Software")]
[assembly: AssemblyProduct("Nightwave.Net")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: NeutralResourcesLanguage("en-GB")]

View File

@ -1,26 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WinPlaza.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -1,20 +0,0 @@
# WinPlaza (Nightwave.NET)
An unofficial client for [Nightwave Plaza][1], which aims to implement as much functionality of the site as possible while maintaining aesthetics.
## First-Party
Nightwave.NET uses *libplaza*, a basic .Net interface for the Nightwave Plaza API.
## Third-Party
Nightwave.NET uses the following third-party libraries and resources:
* [BASS][2] by Un4Seen Developments, via .Net wrapper library [ManagedBass][3] by Mathew Sachin, for audio streaming and playback functionality.
* [JSON.Net][4] by Newtonsoft for deserialisation of JSON strings.
* [CredentialManager][5] by Meziantou for access to the Windows Credential Manager APIs
[1]: https://plaza.one
[2]: https://www.un4seen.com/bass.html
[3]: https://github.com/ManagedBass/ManagedBass
[4]: https://github.com/JamesNK/Newtonsoft.Json
[5]: https://github.com/meziantou/Meziantou.Framework

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

View File

@ -1,197 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{72EEB593-D74D-4775-875A-CD0078923EC3}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>WinPlaza</RootNamespace>
<AssemblyName>WinPlaza</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<IsWebBootstrapper>true</IsWebBootstrapper>
<PublishUrl>c:\swpub\plaza\</PublishUrl>
<Install>true</Install>
<InstallFrom>Web</InstallFrom>
<UpdateEnabled>true</UpdateEnabled>
<UpdateMode>Background</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<InstallUrl>https://swdist.ext.maff.scot/nightwave.net/</InstallUrl>
<ProductName>Nightwave.Net</ProductName>
<PublisherName>Maff</PublisherName>
<CreateWebPageOnPublish>true</CreateWebPageOnPublish>
<WebPage>index.html</WebPage>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<ApplicationRevision>4</ApplicationRevision>
<ApplicationVersion>1.5.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<CreateDesktopShortcut>true</CreateDesktopShortcut>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>
</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\app.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<ManifestCertificateThumbprint>56AE8D286B533A344BB2F6F2F383B107F823DF22</ManifestCertificateThumbprint>
</PropertyGroup>
<PropertyGroup>
<ManifestKeyFile>WinPlaza_TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<PropertyGroup>
<GenerateManifests>true</GenerateManifests>
</PropertyGroup>
<PropertyGroup>
<SignManifests>false</SignManifests>
</PropertyGroup>
<PropertyGroup>
<StartupObject>WinPlaza.App</StartupObject>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>SNSigningKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="ManagedBass, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ManagedBass.2.0.4\lib\net45\ManagedBass.dll</HintPath>
</Reference>
<Reference Include="Meziantou.Framework.Win32.CredentialManager, Version=1.3.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Meziantou.Framework.Win32.CredentialManager.1.3.4\lib\net461\Meziantou.Framework.Win32.CredentialManager.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<None Include="app.manifest" />
<None Include="LICENSE.md" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<None Include="README.md" />
<None Include="SNSigningKey.snk" />
<None Include="WinPlaza_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libplaza\libplaza.csproj">
<Project>{b8cd29c0-4faf-4a02-b00c-3180e60841d3}</Project>
<Name>libplaza</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\app.ico" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\usr.ico" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Content Include="bass.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Resource Include="Resources\playstop.png" />
<Resource Include="Resources\music.png" />
<Resource Include="Resources\usr.png" />
<Resource Include="Resources\app.png" />
<Resource Include="Resources\muted.png" />
<Resource Include="Resources\nomusic.png" />
<Resource Include="Resources\unmuted.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="bin\Debug\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ManagedBass" version="2.0.4" targetFramework="net472" />
<package id="Meziantou.Framework.Win32.CredentialManager" version="1.3.4" targetFramework="net472" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
</packages>

40
libplaza/LICENSE.md Normal file
View File

@ -0,0 +1,40 @@
# libplaza license
Copyright 2019, [Matthew Connelly][1]
## BSD-3-Clause
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[1]: https://maff.scot
# Third-Party Licenses & Acknowledgements
## Newtonsoft JSON.Net
Copyright (c) 2007 James Newton-King
### The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,94 +1,437 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace libplaza {
internal class PlazaApi {
internal const string ApiBase="https://api.plaza.one/";
internal const string ArtworkBase="https://plaza.one/";
internal const string RadioBase="https://radio.plaza.one/";
}
#region API models
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaPlayback {
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string artist;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string title;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string album;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public int length;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public int position;
public long updated;
public string artwork;
public string artwork_s;
public int likes;
public int hates;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string artwork_src;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string artwork_sm_src;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public int reactions;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
//public int hates;
}
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaJson {
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public bool maintenance;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public int listeners;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public long updated_at;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
[JsonProperty]
public PlazaPlayback playback;
public PlazaPlayback song;
}
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaUser {
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public int id;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string username;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string email;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public int role;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string created_at;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public PlazaCurrentVote current_like;
}
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaVote {
public string status;
public int[] ratings;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string result;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public int reactions;
}
public class PlazaLogonResult {
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaGetVote {
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public int reaction;
}
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaCurrentVote {
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public bool like_id;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public PlazaLikedSong song;
}
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaLikedSong {
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string artist;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string artwork_src;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string title;
}
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaLogoutResult {
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string status;
}
/// <summary>
/// A transitional class used internally by <see cref="JsonConvert.DeserializeObject{T}(string)"/>
/// </summary>
public class PlazaLogonResult {
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string result;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string username;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string email;
/// <summary>
/// A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
/// </summary>
public string token;
}
#endregion
#region Exceptions
/// <summary>
/// An <see cref="Exception"/> object indicating that the <see cref="Nightwave"/> API is under maintenance
/// </summary>
internal class PlazaInMaintenanceException : Exception { }
/// <summary>
/// An <see cref="Exception"/> object indicating that an error or exception occurred when communicating with the <see cref="Nightwave"/> API.
/// </summary>
internal class PlazaUnavailableException : Exception { }
/// <summary>
/// A <see cref="PlazaUnavailableException"/> object indicating that, while an error occurred, it's one that we can silently retry.
/// </summary>
internal class PlazaQuietlyUnavailableException : PlazaUnavailableException { }
#endregion
#region Type Definitions
/// <summary>
///Represents the current broadcast information returned by a <see cref="Nightwave"/> API object.
/// </summary>
public class Status {
/// <summary>
/// Indicates whether the <see cref="Nightwave"/> API is in maintenance mode
/// </summary>
public bool InMaintenance;
/// <summary>
/// Indicates whether an exception occurred while querying the <see cref="Nightwave"/> API
/// </summary>
public bool FaultOccurred;
/// <summary>
/// Contains the currently-playing track's title
/// </summary>
public string Title;
/// <summary>
/// Contains the currently-playing track's creating artist
/// </summary>
public string Artist;
/// <summary>
/// Contains the currently-playing track's album, if any
/// </summary>
public string Album;
/// <summary>
/// Contains a relative URI to the artwork for the currently-playing track
/// </summary>
public string ArtworkUri;
/// <summary>
/// Contains an integer value of the elapsed seconds for the current track
/// </summary>
public int Elapsed;
/// <summary>
/// Contains an integer value of the duration in seconds of the current track
/// </summary>
public int Duration;
/// <summary>
/// Contains a 64-bit integer value of the epoch timestamp when the broadcast information was updated
/// </summary>
public long Since;
/// <summary>
/// Contains an integer value of the time difference between the local computer and the <see cref="Nightwave"/> API server
/// </summary>
public int ServerTimeOffset;
public int CalculatedElapsed =>
/// <summary>
/// Returns a dynamically-generated integer value of the elapsed seconds, taking time offsets and time since <see cref="Since"/>
/// </summary>
public int CalculatedElapsed =>
(int)(DateTimeOffset.Now.ToUnixTimeSeconds() - ServerTimeOffset - Since) + Elapsed;
/// <summary>
/// Contains an integer value of the number of likes the currently-playing track has received
/// </summary>
public int Likes;
public int Dislikes;
/// <summary>
/// Contains an integer value of the number of dislikes the currently-playing track has received
/// </summary>
//public int Dislikes;
/// <summary>
/// Contains an integer value of the number of people currently listening to the broadcast
/// </summary>
public int Listeners;
}
public class Nightwave {
public int MinRefreshTime=10;
/// <summary>
/// Represents the user's disposition towards the currently-playing track.
/// </summary>
public enum Vote {
/// <summary>
/// To <c>Dislike</c> is to indicate the user's disposition towards this track is one of displeasure;
/// that the user does not appreciate hearing this track, perhaps even that the user wishes not to hear this track.
/// This field was removed at some point, because it "no longer makes any sense" - I disagree.
/// </summary>
//Dislike = -1,
/// <summary>
/// To be of <c>Neutral</c> disposition is to indicate the user has no feelings towards this track,
/// or that the user does have feelings, but not strong enough to be worth expressing
/// </summary>
Neutral,
/// <summary>
/// To <c>Like</c> is to indicate the user's disposition towards this track is one of enjoyment or pleasure;
/// that the user appreciated this track and/or wishes to indicate such.
/// </summary>
Like,
/// <summary>
/// To <c>Favourite</c> is functionally equivalent to <see cref="Like"/>, with the additional effect of
/// adding the track to the user's favourites list.
/// </summary>
Favourite
}
#endregion
#region Nightwave
/// <summary>
/// <see cref="Nightwave"/> is the primary class by which the Nightwave Plaza API can be used.
/// </summary>
public class Nightwave : IDisposable {
/// <summary>
/// <see cref="MinRefreshTime"/> represents the minimum number of seconds that must pass before the cached <see cref="Status"/> may be invalidated
/// </summary>
public int MinRefreshTime;
private long tsLastRefresh=0;
private static HttpClient _hcl=new HttpClient();
public enum Vote {
Dislike=-1,
Neutral,
Like,
Favourite
}
private Status _bc;
public Nightwave() {
_hcl.BaseAddress = new Uri(PlazaApi.ApiBase);
/// <summary>
/// Creates a new <see cref="Nightwave"/> instance.
/// </summary>
/// <param name="baseuri">Specify an alternate URI for the API - useful if testing a dev server.</param>
/// <example>
/// To create a <see cref="Nightwave"/> API object targeting the production Nightwave Plaza API:
/// <code>
/// <see cref="Nightwave"/> nw = new <see cref="Nightwave"/>();
/// </code>
/// To create a <see cref="Nightwave"/> API object targeting a local server (eg., during development):
/// <code><see cref="Nightwave"/> nwd = new <see cref="Nightwave"/>("http://localhost:8000");</code>
/// </example>
public Nightwave(string baseuri = "https://api.plaza.one/") {
MinRefreshTime = 15;
_hcl.BaseAddress = new Uri(baseuri);
_hcl.Timeout = TimeSpan.FromSeconds(2);
_hcl.DefaultRequestHeaders.Accept.Clear();
_hcl.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<Status> Broadcast(bool Force = false) {
if(!Force && tsLastRefresh + MinRefreshTime >= DateTimeOffset.Now.ToUnixTimeSeconds() && _bc.CalculatedElapsed < _bc.Duration)
return _bc;
await Update();
tsLastRefresh = DateTimeOffset.Now.ToUnixTimeSeconds();
/// <summary>
/// <see cref="Nightwave"/> object deconstructor. Simply a wrapper to ensure that <see cref="Dispose"/> is called.
/// </summary>
~Nightwave() =>
Dispose();
/// <summary>
/// De-initialises the <see cref="Nightwave"/> API object.
/// <para>Currently, this is equivalent to synchronously logging out the current session.</para>
/// </summary>
public void Dispose() =>
Task.Run(async () => await Logout());
/// <summary>
/// Retrieves the current Nightwave Plaza radio broadcast information, if possible
/// </summary>
/// <example>
/// If Nightwave Plaza is broadcasting normally:
/// <code>
/// <see cref="Nightwave"/> n = new Nightwave();
/// <see cref="Status"/> s = await n.Status();
/// Console.WriteLine(s.Title);
/// </code>
/// The Console output would show "リサフランク420 / 現代のコンピュー", if that track were playing.
/// In the event that an HTTP request fails or Nightwave Plaza is in maintenance mode, the appropriate flag will be set
/// <code>
/// <see cref="Nightwave"/> n = new Nightwave();
/// <see cref="Status"/> s = await n.Status();
/// if (s.InMaintenance) {
/// Console.WriteLine("In maintenance!");
/// return;
/// } else if (s.FaultOccurred) {
/// Console.WriteLine("A fault occurred!");
/// return;
/// }
/// Console.WriteLine(s.Title);
/// </code>
/// The Console output would show "In maintenance!" or "A fault occurred!", depending on the conditions.
/// </example>
/// <remarks>
/// This method has inherent caching, and thus will not necessarily result in the <see cref="Status"/> object returned being fully fresh.
/// The <see cref="Status"/> object being returned may be a cached object UNLESS the <paramref name="Force"/> parameter is true
/// OR the <see cref="Status"/> object is older than the number of seconds stored by <see cref="MinRefreshTime"/>
/// OR <see cref="Status.CalculatedElapsed"/> is equal to or greater than <see cref="Status.Duration"/>.
/// </remarks>
/// <param name="Force">If true, the API will always be queried, and no caching will be performed.</param>
/// <returns>A populated <see cref="Status"/> object describing the current broadcast details.</returns>
public async Task<Status> Status(bool Force = false) {
//An API call will ONLY be made if Force is set, if the cached data is "stale",
// or if the currently-playing track's elapsed time is more than the track duration.
if(Force
|| (tsLastRefresh + MinRefreshTime < DateTimeOffset.Now.ToUnixTimeSeconds())
|| _bc.CalculatedElapsed > _bc.Duration) {
await Update();
tsLastRefresh = DateTimeOffset.Now.ToUnixTimeSeconds();
}
return _bc;
}
/// <summary>
/// A basic wrapper around <see cref="GetNightwaveAsync"/> which provides basic error handling.
/// </summary>
/// <returns>If called with <c>await</c>, returns nothing. If called without, returns a <c>Task</c> object for the method call</returns>
public async Task Update() {
try {
Status _=await GetNightwaveAsync();
@ -98,85 +441,172 @@ namespace libplaza {
} catch(PlazaInMaintenanceException) {
_bc.InMaintenance = true;
_bc.FaultOccurred = false;
} catch(PlazaQuietlyUnavailableException) {
_bc.FaultOccurred = false;
} catch(PlazaUnavailableException) {
_bc.FaultOccurred = true;
} catch(Exception) { //TODO make this more reliable?
_bc.FaultOccurred = true;
}
}
public async void EvtUpdate() => await Update();
/// <summary>
/// Communicates with the <see cref="Nightwave"/> API to retrieve the current broadcast information.
/// </summary>
/// <returns>Returns a <c>Status</c> object representing the current broadcast information.</returns>
/// <exception cref="PlazaUnavailableException">May be thrown in event of a non-success (HTTP/1.1 2XX) return code</exception>
/// <exception cref="PlazaInMaintenanceException">May be thrown in event that the API indicates it is in maintenance</exception>
public async Task<Status> GetNightwaveAsync() {
Status status;
HttpResponseMessage _r=await _hcl.GetAsync("/status");
if(_r.StatusCode == HttpStatusCode.TooManyRequests)
throw new PlazaQuietlyUnavailableException();
if(!_r.IsSuccessStatusCode)
throw new PlazaUnavailableException();
//string _=await _r.Content.ReadAsStringAsync();
PlazaJson j=JsonConvert.DeserializeObject<PlazaJson>(await _r.Content.ReadAsStringAsync());
if(j.maintenance)
throw new PlazaInMaintenanceException();
// Previously this calculated ServerTimeOffset as Now - Headers.Date.Value.ToUnixTimeSeconds
status = new Status {
Title = j.playback.title,
Album = j.playback.album,
Artist = j.playback.artist,
Elapsed = j.playback.position,
Duration = j.playback.length,
Since = j.playback.updated,
Likes = j.playback.likes,
Dislikes = j.playback.hates,
Title = j.song.title,
Album = j.song.album,
Artist = j.song.artist,
Elapsed = j.song.position,
Duration = j.song.length,
Since = j.updated_at,
Likes = j.song.reactions,
Listeners = j.listeners,
ServerTimeOffset = (int)(DateTimeOffset.Now.ToUnixTimeSeconds() - _r.Headers.Date.Value.ToUnixTimeSeconds()),
ArtworkUri = $"{PlazaApi.ArtworkBase}{j.playback.artwork}"
ServerTimeOffset = (int)(DateTimeOffset.Now.ToUnixTimeSeconds() - j.updated_at),
ArtworkUri = j.song.artwork_src
};
return status;
}
public async Task<bool> CastVote(Vote v) {
/// <summary>
/// Fetches the user's <see cref="Vote"/> for the currently-playing track.
/// </summary>
/// <returns>A <see cref="Vote"/> object indicating the user's disposition towards the track. If the vote could not be returned, this will be <c>null</c></returns>
public async Task<Vote?> Vote() {
if(!await CheckSession())
return null;
PlazaUser r=await GetUser();
if(r.current_like == null)
return libplaza.Vote.Neutral;
if(r.current_like.like_id)
return libplaza.Vote.Favourite;
return libplaza.Vote.Like;
}
/// <summary>
/// Casts the given <see cref="Vote"/> specified in <paramref name="v"/> as the user's vote on the current track.
/// </summary>
/// <param name="v">A <see cref="Vote"/> object indicating the user's disposition towards the current track</param>
/// <returns>A <c>bool</c>ean value indicating the success of the vote operation</returns>
public async Task<bool> Vote(Vote v) {
if(!await CheckSession())
return false;
HttpResponseMessage _r=await _hcl.PostAsync("/vote", new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("rate", ((int)v).ToString())}));
HttpResponseMessage _r=await _hcl.PostAsync("/reactions", new StringContent(
$"{{\"reaction\":{(int)v}}}", Encoding.UTF8, "application/json"));
if(!_r.IsSuccessStatusCode)
return false;
PlazaVote r=JsonConvert.DeserializeObject<PlazaVote>(await _r.Content.ReadAsStringAsync());
if(r.status != "success")
return false;
return true;
return r.result == "success";
}
/// <summary>
/// An overload method to <see cref="CheckSession"/> to first set the <c>X-Access-Token</c> header to <paramref name="str"/>
/// </summary>
/// <param name="str">A valid session token for the <see cref="Nightwave"/> API</param>
/// <returns>A <c>bool</c>ean value indicating whether the token given in <paramref name="str"/> was able to perform an authenticated API request</returns>
public async Task<bool> CheckSession(string str) {
_=_hcl.DefaultRequestHeaders.Remove("X-Access-Token");
_ = _hcl.DefaultRequestHeaders.Remove("X-Access-Token");
_hcl.DefaultRequestHeaders.Add("X-Access-Token", str);
return await CheckSession();
}
public async Task<bool> CheckSession() {
if(await GetUser() != null)
return true;
return false;
}
public async Task<string> GetUser() {
/// <summary>
/// Performs a basic read-only API request to determine whether the session token, if present, is valid.
/// </summary>
/// <returns>A <c>bool</c>ean value indicating whether the request succeeded</returns>
public async Task<bool> CheckSession() =>
await GetUsername() != null;
/// <summary>
/// Performs a basic read-only API request to fetch the profile information for the current user
/// </summary>
/// <returns>If successful, returns a <see cref="PlazaUser"/> containing the user's profile information. If unsuccessful, returns null.</returns>
public async Task<PlazaUser> GetUser() {
HttpResponseMessage _r=await _hcl.GetAsync("/user");
if(!_r.IsSuccessStatusCode)
return null;
PlazaUser r=JsonConvert.DeserializeObject<PlazaUser>(await _r.Content.ReadAsStringAsync());
if(r.id <= 0)
return null;
return r.username;
return r;
}
public async Task<bool> Login(string u, string p) => await Login(Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{u}:{p}")));
/// <summary>
/// Performs a basic read-only API request to fetch the currently-authenticated user's profile details.
/// </summary>
/// <returns>If successful, returns a <c>string</c> containing the authenticated user's username. If unsuccessful, will return null.</returns>
public async Task<string> GetUsername() {
PlazaUser r=await GetUser();
return (r==null || r.username==null || r.username?.Length <= 0) ? null : r.username;
}
/// <summary>
/// Performs a login request to the <see cref="Nightwave"/> API with the given <paramref name="u">username</paramref> and <paramref name="p">password</paramref>.
/// </summary>
/// <param name="u">A <c>string</c> containing the user's username</param>
/// <param name="p">A <c>string</c> containing the user's password</param>
/// <returns>A <c>bool</c>ean value indicating whether the login attempt succeeded</returns>
/// <remarks>
/// Functionally, this method is a wrapper for <see cref="Login(string)"/>, performing the function of generating a Basic authentication string
/// </remarks>
public async Task<bool> Login(string u, string p) {
_hcl.DefaultRequestHeaders.Add("Authorization", "Bearer 1");
HttpResponseMessage _r=await _hcl.PostAsync("/user/auth", new StringContent(
$"{{\"username\":\"{u}\",\"password\":\"{p}\"}}", Encoding.UTF8, "application/json"));
_ = _hcl.DefaultRequestHeaders.Remove("Authorization");
if(_r.StatusCode == HttpStatusCode.TooManyRequests)
return false;
if(!_r.IsSuccessStatusCode)
return false;
PlazaLogonResult r=JsonConvert.DeserializeObject<PlazaLogonResult>(await _r.Content.ReadAsStringAsync());
if(r.result != "ok")
return false;
_hcl.DefaultRequestHeaders.Add("Authorization", $"Bearer {r.token}");
return true;
}
/// <summary>
/// Performs a login request to the <see cref="Nightwave"/> API with the given HTTP Basic authentication <paramref name="str">string</paramref>.
/// </summary>
/// <param name="str">A valid HTTP Basic authentication string (<c>md5(username:password)</c>)</param>
/// <returns>A <c>bool</c>ean value indicating whether the login attempt succeeded</returns>
public async Task<bool> Login(string str) {
_hcl.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", str);
HttpResponseMessage _r=await _hcl.PostAsync("/user/login", null);
if(!_r.IsSuccessStatusCode)
return false;
PlazaLogonResult r=JsonConvert.DeserializeObject<PlazaLogonResult>(await _r.Content.ReadAsStringAsync());
if(r.status != "success")
if(r.result != "success")
return false;
_hcl.DefaultRequestHeaders.Add("X-Access-Token", r.token);
_=_hcl.DefaultRequestHeaders.Remove("Authorization");
_ = _hcl.DefaultRequestHeaders.Remove("Authorization");
return true;
}
#if DEBUG
public string GetAuthorizationToken() {
IEnumerator<string> h=_hcl.DefaultRequestHeaders.GetValues("X-Access-Token").GetEnumerator();
_ = h.MoveNext();
return h.Current;
/// <summary>
/// Performs a logout request to the <see cref="Nightwave"/> API.
/// </summary>
/// <returns>If called with <c>await</c>, returns nothing. Otherwise, returns a <c>Task</c> object representing the method call</returns>
public async Task Logout() {
//Any API call errors are assumed to indicate an invalid session
// Since the goal of a logout function is to not have a valid session, this is a good thing.
if(await CheckSession())
_ = await _hcl.PostAsync("/user/logout", null);
_ = _hcl.DefaultRequestHeaders.Remove("Authorization");
_ = _hcl.DefaultRequestHeaders.Remove("X-Access-Token");
}
#endif
}
#endregion
}

View File

@ -1,11 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net6.0-windows</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<NeutralLanguage>en-GB</NeutralLanguage>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
<PackageLicenseExpression>BSD-3-CLAUSE</PackageLicenseExpression>
<Authors>Maff</Authors>
<Company>Softpup Aesthetics &amp; Petting, p.r.p.l.</Company>
<Description>A moderately generic .Net interface to the Nightwave Plaza API</Description>
<Copyright>Nightwave Plaza is wholly copyright and owned by its creator, I just wrote this unofficial library that talks to it.</Copyright>
<PackageProjectUrl>https://soft.pup.cloud/Nightwave.NET/</PackageProjectUrl>
<RepositoryUrl>https://commit.pup.cloud/maff/PlazaSharp/src/branch/master/libplaza</RepositoryUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants />
<DocumentationFile>C:\Users\maff\source\repos\PlazaSharp\libplaza\libplaza.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
</ItemGroup>

463
libplaza/libplaza.xml Normal file
View File

@ -0,0 +1,463 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>libplaza</name>
</assembly>
<members>
<member name="T:libplaza.PlazaPlayback">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaPlayback.artist">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaPlayback.title">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaPlayback.album">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaPlayback.length">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaPlayback.position">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaPlayback.artwork_src">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaPlayback.artwork_sm_src">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaPlayback.reactions">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaJson">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaJson.maintenance">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaJson.listeners">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaJson.updated_at">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaJson.song">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaUser">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaUser.id">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaUser.username">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaUser.email">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaUser.role">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaUser.created_at">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaUser.current_like">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaVote">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaVote.result">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaVote.reactions">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaGetVote">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaGetVote.reaction">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaCurrentVote">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaCurrentVote.like_id">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaCurrentVote.song">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaLikedSong">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaLikedSong.artist">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaLikedSong.artwork_src">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaLikedSong.title">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaLogoutResult">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaLogoutResult.status">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaLogonResult">
<summary>
A transitional class used internally by <see cref="M:Newtonsoft.Json.JsonConvert.DeserializeObject``1(System.String)"/>
</summary>
</member>
<member name="F:libplaza.PlazaLogonResult.result">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaLogonResult.username">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaLogonResult.email">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="F:libplaza.PlazaLogonResult.token">
<summary>
A representation of a JSON object attribute, used transitionally while processing a deserialized JSON payload
</summary>
</member>
<member name="T:libplaza.PlazaInMaintenanceException">
<summary>
An <see cref="T:System.Exception"/> object indicating that the <see cref="T:libplaza.Nightwave"/> API is under maintenance
</summary>
</member>
<member name="T:libplaza.PlazaUnavailableException">
<summary>
An <see cref="T:System.Exception"/> object indicating that an error or exception occurred when communicating with the <see cref="T:libplaza.Nightwave"/> API.
</summary>
</member>
<member name="T:libplaza.PlazaQuietlyUnavailableException">
<summary>
A <see cref="T:libplaza.PlazaUnavailableException"/> object indicating that, while an error occurred, it's one that we can silently retry.
</summary>
</member>
<member name="T:libplaza.Status">
<summary>
Represents the current broadcast information returned by a <see cref="T:libplaza.Nightwave"/> API object.
</summary>
</member>
<member name="F:libplaza.Status.InMaintenance">
<summary>
Indicates whether the <see cref="T:libplaza.Nightwave"/> API is in maintenance mode
</summary>
</member>
<member name="F:libplaza.Status.FaultOccurred">
<summary>
Indicates whether an exception occurred while querying the <see cref="T:libplaza.Nightwave"/> API
</summary>
</member>
<member name="F:libplaza.Status.Title">
<summary>
Contains the currently-playing track's title
</summary>
</member>
<member name="F:libplaza.Status.Artist">
<summary>
Contains the currently-playing track's creating artist
</summary>
</member>
<member name="F:libplaza.Status.Album">
<summary>
Contains the currently-playing track's album, if any
</summary>
</member>
<member name="F:libplaza.Status.ArtworkUri">
<summary>
Contains a relative URI to the artwork for the currently-playing track
</summary>
</member>
<member name="F:libplaza.Status.Elapsed">
<summary>
Contains an integer value of the elapsed seconds for the current track
</summary>
</member>
<member name="F:libplaza.Status.Duration">
<summary>
Contains an integer value of the duration in seconds of the current track
</summary>
</member>
<member name="F:libplaza.Status.Since">
<summary>
Contains a 64-bit integer value of the epoch timestamp when the broadcast information was updated
</summary>
</member>
<member name="F:libplaza.Status.ServerTimeOffset">
<summary>
Contains an integer value of the time difference between the local computer and the <see cref="T:libplaza.Nightwave"/> API server
</summary>
</member>
<member name="P:libplaza.Status.CalculatedElapsed">
<summary>
Returns a dynamically-generated integer value of the elapsed seconds, taking time offsets and time since <see cref="F:libplaza.Status.Since"/>
</summary>
</member>
<member name="F:libplaza.Status.Likes">
<summary>
Contains an integer value of the number of likes the currently-playing track has received
</summary>
</member>
<member name="F:libplaza.Status.Listeners">
<summary>
Contains an integer value of the number of people currently listening to the broadcast
</summary>
</member>
<member name="T:libplaza.Vote">
<summary>
Represents the user's disposition towards the currently-playing track.
</summary>
</member>
<member name="F:libplaza.Vote.Neutral">
<summary>
To be of <c>Neutral</c> disposition is to indicate the user has no feelings towards this track,
or that the user does have feelings, but not strong enough to be worth expressing
</summary>
</member>
<member name="F:libplaza.Vote.Like">
<summary>
To <c>Like</c> is to indicate the user's disposition towards this track is one of enjoyment or pleasure;
that the user appreciated this track and/or wishes to indicate such.
</summary>
</member>
<member name="F:libplaza.Vote.Favourite">
<summary>
To <c>Favourite</c> is functionally equivalent to <see cref="F:libplaza.Vote.Like"/>, with the additional effect of
adding the track to the user's favourites list.
</summary>
</member>
<member name="T:libplaza.Nightwave">
<summary>
<see cref="T:libplaza.Nightwave"/> is the primary class by which the Nightwave Plaza API can be used.
</summary>
</member>
<member name="F:libplaza.Nightwave.MinRefreshTime">
<summary>
<see cref="F:libplaza.Nightwave.MinRefreshTime"/> represents the minimum number of seconds that must pass before the cached <see cref="M:libplaza.Nightwave.Status(System.Boolean)"/> may be invalidated
</summary>
</member>
<member name="M:libplaza.Nightwave.#ctor(System.String)">
<summary>
Creates a new <see cref="T:libplaza.Nightwave"/> instance.
</summary>
<param name="baseuri">Specify an alternate URI for the API - useful if testing a dev server.</param>
<example>
To create a <see cref="T:libplaza.Nightwave"/> API object targeting the production Nightwave Plaza API:
<code>
<see cref="T:libplaza.Nightwave"/> nw = new <see cref="T:libplaza.Nightwave"/>();
</code>
To create a <see cref="T:libplaza.Nightwave"/> API object targeting a local server (eg., during development):
<code><see cref="T:libplaza.Nightwave"/> nwd = new <see cref="T:libplaza.Nightwave"/>("http://localhost:8000");</code>
</example>
</member>
<member name="M:libplaza.Nightwave.Finalize">
<summary>
<see cref="T:libplaza.Nightwave"/> object deconstructor. Simply a wrapper to ensure that <see cref="M:libplaza.Nightwave.Dispose"/> is called.
</summary>
</member>
<member name="M:libplaza.Nightwave.Dispose">
<summary>
De-initialises the <see cref="T:libplaza.Nightwave"/> API object.
<para>Currently, this is equivalent to synchronously logging out the current session.</para>
</summary>
</member>
<member name="M:libplaza.Nightwave.Status(System.Boolean)">
<summary>
Retrieves the current Nightwave Plaza radio broadcast information, if possible
</summary>
<example>
If Nightwave Plaza is broadcasting normally:
<code>
<see cref="T:libplaza.Nightwave"/> n = new Nightwave();
<see cref="M:libplaza.Nightwave.Status(System.Boolean)"/> s = await n.Status();
Console.WriteLine(s.Title);
</code>
The Console output would show "リサフランク420 / 現代のコンピュー", if that track were playing.
In the event that an HTTP request fails or Nightwave Plaza is in maintenance mode, the appropriate flag will be set
<code>
<see cref="T:libplaza.Nightwave"/> n = new Nightwave();
<see cref="M:libplaza.Nightwave.Status(System.Boolean)"/> s = await n.Status();
if (s.InMaintenance) {
Console.WriteLine("In maintenance!");
return;
} else if (s.FaultOccurred) {
Console.WriteLine("A fault occurred!");
return;
}
Console.WriteLine(s.Title);
</code>
The Console output would show "In maintenance!" or "A fault occurred!", depending on the conditions.
</example>
<remarks>
This method has inherent caching, and thus will not necessarily result in the <see cref="M:libplaza.Nightwave.Status(System.Boolean)"/> object returned being fully fresh.
The <see cref="M:libplaza.Nightwave.Status(System.Boolean)"/> object being returned may be a cached object UNLESS the <paramref name="Force"/> parameter is true
OR the <see cref="M:libplaza.Nightwave.Status(System.Boolean)"/> object is older than the number of seconds stored by <see cref="F:libplaza.Nightwave.MinRefreshTime"/>
OR <see cref="P:libplaza.Status.CalculatedElapsed"/> is equal to or greater than <see cref="F:libplaza.Status.Duration"/>.
</remarks>
<param name="Force">If true, the API will always be queried, and no caching will be performed.</param>
<returns>A populated <see cref="M:libplaza.Nightwave.Status(System.Boolean)"/> object describing the current broadcast details.</returns>
</member>
<member name="M:libplaza.Nightwave.Update">
<summary>
A basic wrapper around <see cref="M:libplaza.Nightwave.GetNightwaveAsync"/> which provides basic error handling.
</summary>
<returns>If called with <c>await</c>, returns nothing. If called without, returns a <c>Task</c> object for the method call</returns>
</member>
<member name="M:libplaza.Nightwave.GetNightwaveAsync">
<summary>
Communicates with the <see cref="T:libplaza.Nightwave"/> API to retrieve the current broadcast information.
</summary>
<returns>Returns a <c>Status</c> object representing the current broadcast information.</returns>
<exception cref="T:libplaza.PlazaUnavailableException">May be thrown in event of a non-success (HTTP/1.1 2XX) return code</exception>
<exception cref="T:libplaza.PlazaInMaintenanceException">May be thrown in event that the API indicates it is in maintenance</exception>
</member>
<member name="M:libplaza.Nightwave.Vote">
<summary>
Fetches the user's <see cref="M:libplaza.Nightwave.Vote"/> for the currently-playing track.
</summary>
<returns>A <see cref="M:libplaza.Nightwave.Vote"/> object indicating the user's disposition towards the track. If the vote could not be returned, this will be <c>null</c></returns>
</member>
<member name="M:libplaza.Nightwave.Vote(libplaza.Vote)">
<summary>
Casts the given <see cref="M:libplaza.Nightwave.Vote"/> specified in <paramref name="v"/> as the user's vote on the current track.
</summary>
<param name="v">A <see cref="M:libplaza.Nightwave.Vote"/> object indicating the user's disposition towards the current track</param>
<returns>A <c>bool</c>ean value indicating the success of the vote operation</returns>
</member>
<member name="M:libplaza.Nightwave.CheckSession(System.String)">
<summary>
An overload method to <see cref="M:libplaza.Nightwave.CheckSession(System.String)"/> to first set the <c>X-Access-Token</c> header to <paramref name="str"/>
</summary>
<param name="str">A valid session token for the <see cref="T:libplaza.Nightwave"/> API</param>
<returns>A <c>bool</c>ean value indicating whether the token given in <paramref name="str"/> was able to perform an authenticated API request</returns>
</member>
<member name="M:libplaza.Nightwave.CheckSession">
<summary>
Performs a basic read-only API request to determine whether the session token, if present, is valid.
</summary>
<returns>A <c>bool</c>ean value indicating whether the request succeeded</returns>
</member>
<member name="M:libplaza.Nightwave.GetUser">
<summary>
Performs a basic read-only API request to fetch the profile information for the current user
</summary>
<returns>If successful, returns a <see cref="T:libplaza.PlazaUser"/> containing the user's profile information. If unsuccessful, returns null.</returns>
</member>
<member name="M:libplaza.Nightwave.GetUsername">
<summary>
Performs a basic read-only API request to fetch the currently-authenticated user's profile details.
</summary>
<returns>If successful, returns a <c>string</c> containing the authenticated user's username. If unsuccessful, will return null.</returns>
</member>
<member name="M:libplaza.Nightwave.Login(System.String,System.String)">
<summary>
Performs a login request to the <see cref="T:libplaza.Nightwave"/> API with the given <paramref name="u">username</paramref> and <paramref name="p">password</paramref>.
</summary>
<param name="u">A <c>string</c> containing the user's username</param>
<param name="p">A <c>string</c> containing the user's password</param>
<returns>A <c>bool</c>ean value indicating whether the login attempt succeeded</returns>
<remarks>
Functionally, this method is a wrapper for <see cref="M:libplaza.Nightwave.Login(System.String)"/>, performing the function of generating a Basic authentication string
</remarks>
</member>
<member name="M:libplaza.Nightwave.Login(System.String)">
<summary>
Performs a login request to the <see cref="T:libplaza.Nightwave"/> API with the given HTTP Basic authentication <paramref name="str">string</paramref>.
</summary>
<param name="str">A valid HTTP Basic authentication string (<c>md5(username:password)</c>)</param>
<returns>A <c>bool</c>ean value indicating whether the login attempt succeeded</returns>
</member>
<member name="M:libplaza.Nightwave.Logout">
<summary>
Performs a logout request to the <see cref="T:libplaza.Nightwave"/> API.
</summary>
<returns>If called with <c>await</c>, returns nothing. Otherwise, returns a <c>Task</c> object representing the method call</returns>
</member>
</members>
</doc>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View File

@ -1,133 +0,0 @@
namespace testplaza {
partial class Form1 {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if(disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.pbAlbumArt = new System.Windows.Forms.PictureBox();
this.txInfo = new System.Windows.Forms.TextBox();
this.btRefresh = new System.Windows.Forms.Button();
this.btLike = new System.Windows.Forms.Button();
this.btDislike = new System.Windows.Forms.Button();
this.lbLikes = new System.Windows.Forms.Label();
this.lbDislikes = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.pbAlbumArt)).BeginInit();
this.SuspendLayout();
//
// pbAlbumArt
//
this.pbAlbumArt.Location = new System.Drawing.Point(12, 12);
this.pbAlbumArt.Name = "pbAlbumArt";
this.pbAlbumArt.Size = new System.Drawing.Size(200, 200);
this.pbAlbumArt.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.pbAlbumArt.TabIndex = 0;
this.pbAlbumArt.TabStop = false;
//
// txInfo
//
this.txInfo.Enabled = false;
this.txInfo.Location = new System.Drawing.Point(218, 12);
this.txInfo.Multiline = true;
this.txInfo.Name = "txInfo";
this.txInfo.Size = new System.Drawing.Size(570, 200);
this.txInfo.TabIndex = 1;
//
// btRefresh
//
this.btRefresh.Location = new System.Drawing.Point(218, 218);
this.btRefresh.Name = "btRefresh";
this.btRefresh.Size = new System.Drawing.Size(570, 50);
this.btRefresh.TabIndex = 2;
this.btRefresh.Text = "Refresh";
this.btRefresh.UseVisualStyleBackColor = true;
this.btRefresh.Click += new System.EventHandler(this.BtRefresh_Click);
//
// btLike
//
this.btLike.Location = new System.Drawing.Point(12, 218);
this.btLike.Name = "btLike";
this.btLike.Size = new System.Drawing.Size(81, 50);
this.btLike.TabIndex = 3;
this.btLike.Text = "Like";
this.btLike.UseVisualStyleBackColor = true;
this.btLike.Click += new System.EventHandler(this.BtLike_Click);
//
// btDislike
//
this.btDislike.Location = new System.Drawing.Point(121, 218);
this.btDislike.Name = "btDislike";
this.btDislike.Size = new System.Drawing.Size(91, 50);
this.btDislike.TabIndex = 4;
this.btDislike.Text = "Dislike";
this.btDislike.UseVisualStyleBackColor = true;
this.btDislike.Click += new System.EventHandler(this.BtDislike_Click);
//
// lbLikes
//
this.lbLikes.AutoSize = true;
this.lbLikes.Location = new System.Drawing.Point(99, 222);
this.lbLikes.Name = "lbLikes";
this.lbLikes.Size = new System.Drawing.Size(16, 17);
this.lbLikes.TabIndex = 5;
this.lbLikes.Text = "0";
//
// lbDislikes
//
this.lbDislikes.AutoSize = true;
this.lbDislikes.Location = new System.Drawing.Point(99, 247);
this.lbDislikes.Name = "lbDislikes";
this.lbDislikes.Size = new System.Drawing.Size(16, 17);
this.lbDislikes.TabIndex = 6;
this.lbDislikes.Text = "0";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 280);
this.Controls.Add(this.lbDislikes);
this.Controls.Add(this.lbLikes);
this.Controls.Add(this.btDislike);
this.Controls.Add(this.btLike);
this.Controls.Add(this.btRefresh);
this.Controls.Add(this.txInfo);
this.Controls.Add(this.pbAlbumArt);
this.Name = "Form1";
this.Text = "libplaza validation app";
((System.ComponentModel.ISupportInitialize)(this.pbAlbumArt)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.PictureBox pbAlbumArt;
private System.Windows.Forms.TextBox txInfo;
private System.Windows.Forms.Button btRefresh;
private System.Windows.Forms.Button btLike;
private System.Windows.Forms.Button btDislike;
private System.Windows.Forms.Label lbLikes;
private System.Windows.Forms.Label lbDislikes;
}
}

View File

@ -1,36 +0,0 @@
using System;
using System.Windows.Forms;
using libplaza;
namespace testplaza {
public partial class Form1 : Form {
public Form1() => InitializeComponent();
private Nightwave plaza=new Nightwave();
private void BtLike_Click(object sender, EventArgs e) {
}
private void BtDislike_Click(object sender, EventArgs e) {
}
private async void BtRefresh_Click(object sender, EventArgs e) {
Nightwave.Status now=await plaza.Broadcast();
txInfo.ResetText();
txInfo.AppendText($"Plaza is {plaza.Broadcasting}\n");
if(plaza.Broadcasting == Nightwave.BroadcastStatus.OffAir)
return;
lbLikes.Text = now.Likes.ToString();
lbDislikes.Text = now.Dislikes.ToString();
txInfo.AppendText($"{now.Listeners} listeners\n");
txInfo.AppendText($"Playing {now.Title}\n");
txInfo.AppendText($"From album {now.Album}\n");
txInfo.AppendText($"By {now.Artist}\n");
txInfo.AppendText($"Song is {now.Duration}s long\n");
txInfo.AppendText($"{now.Elapsed}s elapsed (calculated elapsed is {now.CalculatedElapsed}s)\n");
pbAlbumArt.LoadAsync(now.ArtworkUri);
txInfo.AppendText($"Album art is at:\n{now.ArtworkUri}\n");
}
}
}

View File

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace testplaza {
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}

View File

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("testplaza")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("testplaza")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("dab02d69-cd02-4c74-a110-7907e5b4a568")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,62 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace testplaza.Properties {
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if((resourceMan == null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("testplaza.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,26 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace testplaza.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
</packages>

View File

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DAB02D69-CD02-4C74-A110-7907E5B4A568}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>testplaza</RootNamespace>
<AssemblyName>testplaza</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libplaza\libplaza.csproj">
<Project>{B8CD29C0-4FAF-4A02-B00C-3180E60841D3}</Project>
<Name>libplaza</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>