feat(hass): add PlaySong voice command and some netd fixes
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-10-09 11:01:53 -04:00
parent a4d2b936d9
commit 0f109a9a69
12 changed files with 280 additions and 67 deletions

View file

@ -8,6 +8,7 @@ using System.Text.Json;
record PlayAlbumData(string? artist, string? album); record PlayAlbumData(string? artist, string? album);
[NetDaemonApp] [NetDaemonApp]
public class PlayAlbum public class PlayAlbum
{ {
@ -29,8 +30,11 @@ public class PlayAlbum
data: new SpotifyplusSearchAlbumsParameters data: new SpotifyplusSearchAlbumsParameters
{ {
Criteria = $"{e?.artist} {e?.album}", Criteria = $"{e?.artist} {e?.album}",
Limit = 1, LimitTotal = 1,
EntityId = "media_player.spotifyplus" EntityId = Global.DEFAULT_ENTITY_ID,
// My Defaults
Market = "CA",
IncludeExternal = "audio",
} }
)).Value.Deserialize<SpotifyplusSearchAlbumsResponse>(_jsonOptions); )).Value.Deserialize<SpotifyplusSearchAlbumsResponse>(_jsonOptions);
@ -44,7 +48,11 @@ public class PlayAlbum
data: new SpotifyplusPlayerMediaPlayContextParameters data: new SpotifyplusPlayerMediaPlayContextParameters
{ {
ContextUri = uri, ContextUri = uri,
EntityId = "media_player.spotifyplus" EntityId = Global.DEFAULT_ENTITY_ID,
DeviceId = Global.DEFAULT_DEV_ID,
// My Defaults
PositionMs = 0,
Delay = 0.50,
} }
); );
} }

View file

@ -2,11 +2,12 @@ namespace Spotify;
using System.Collections.Generic; using System.Collections.Generic;
public record AlbumItem public record AlbumItem
{ {
public string? AlbumType { get; set; } public string? AlbumType { get; set; }
public List<Artist>? Artists { get; set; } public List<Artist>? Artists { get; set; }
public List<string?>? AvailableMarkets { get; set; } public List<string>? AvailableMarkets { get; set; }
public ExternalUrls? ExternalUrls { get; set; } public ExternalUrls? ExternalUrls { get; set; }
public string? Href { get; set; } public string? Href { get; set; }
public string? Id { get; set; } public string? Id { get; set; }
@ -16,7 +17,7 @@ public record AlbumItem
public string? ReleaseDate { get; set; } public string? ReleaseDate { get; set; }
public string? ReleaseDatePrecision { get; set; } public string? ReleaseDatePrecision { get; set; }
public Restrictions? Restrictions { get; set; } public Restrictions? Restrictions { get; set; }
public int TotalTracks { get; set; } public int? TotalTracks { get; set; }
public string? Type { get; set; } public string? Type { get; set; }
public string? Uri { get; set; } public string? Uri { get; set; }
} }
@ -24,15 +25,15 @@ public record AlbumItem
public record AlbumResult public record AlbumResult
{ {
public string? Href { get; set; } public string? Href { get; set; }
public int Limit { get; set; } public int? Limit { get; set; }
public string? Next { get; set; } public string? Next { get; set; }
public int Offset { get; set; } public int? Offset { get; set; }
public object? Previous { get; set; } public object? Previous { get; set; }
public int Total { get; set; } public int? Total { get; set; }
public List<AlbumItem>? Items { get; set; } public List<AlbumItem>? Items { get; set; }
} }
public class SpotifyplusSearchAlbumsResponse public record SpotifyplusSearchAlbumsResponse
{ {
public UserProfile? UserProfile { get; set; } public UserProfile? UserProfile { get; set; }
public AlbumResult? Result { get; set; } public AlbumResult? Result { get; set; }

View file

@ -8,6 +8,7 @@ using System.Text.Json;
record PlayArtistData(string? artist); record PlayArtistData(string? artist);
[NetDaemonApp] [NetDaemonApp]
public class PlayArtist public class PlayArtist
{ {
@ -29,8 +30,11 @@ public class PlayArtist
data: new SpotifyplusSearchArtistsParameters data: new SpotifyplusSearchArtistsParameters
{ {
Criteria = e?.artist, Criteria = e?.artist,
Limit = 1, LimitTotal = 1,
EntityId = "media_player.spotifyplus" EntityId = Global.DEFAULT_ENTITY_ID,
// My Defaults
Market = "CA",
IncludeExternal = "audio",
} }
)).Value.Deserialize<SpotifyplusSearchArtistsResponse>(_jsonOptions); )).Value.Deserialize<SpotifyplusSearchArtistsResponse>(_jsonOptions);
@ -44,7 +48,11 @@ public class PlayArtist
data: new SpotifyplusPlayerMediaPlayContextParameters data: new SpotifyplusPlayerMediaPlayContextParameters
{ {
ContextUri = uri, ContextUri = uri,
EntityId = "media_player.spotifyplus" EntityId = Global.DEFAULT_ENTITY_ID,
DeviceId = Global.DEFAULT_DEV_ID,
// My Defaults
PositionMs = 0,
Delay = 0.50,
} }
); );
} }

View file

@ -2,17 +2,18 @@ namespace Spotify;
using System.Collections.Generic; using System.Collections.Generic;
public record ArtistItem public record ArtistItem
{ {
public ExternalUrls? External_urls { get; init; } public ExternalUrls? External_urls { get; init; }
public Followers? Followers { get; init; } public Followers? Followers { get; init; }
public List<string?>? Genres { get; init; } public List<string>? Genres { get; init; }
public string? Href { get; init; } public string? Href { get; init; }
public string? Id { get; init; } public string? Id { get; init; }
public string? Image_url { get; init; } public string? Image_url { get; init; }
public List<Image>? Images { get; init; } public List<Image>? Images { get; init; }
public string? Name { get; init; } public string? Name { get; init; }
public int Popularity { get; init; } public int? Popularity { get; init; }
public string? Type { get; init; } public string? Type { get; init; }
public string? Uri { get; init; } public string? Uri { get; init; }
} }
@ -20,11 +21,11 @@ public record ArtistItem
public record ArtistResult public record ArtistResult
{ {
public string? Href { get; init; } public string? Href { get; init; }
public int Limit { get; init; } public int? Limit { get; init; }
public string? Next { get; init; } public string? Next { get; init; }
public int Offset { get; set; } public int? Offset { get; set; }
public object? Previous { get; init; } public object? Previous { get; init; }
public int Total { get; init; } public int? Total { get; init; }
public List<ArtistItem>? Items { get; init; } public List<ArtistItem>? Items { get; init; }
} }

View file

@ -12,6 +12,7 @@ using System.Text.Json;
record PlayPlaylistData(string? playlist); record PlayPlaylistData(string? playlist);
[NetDaemonApp] [NetDaemonApp]
public class PlayPlaylist public class PlayPlaylist
{ {
@ -34,9 +35,9 @@ public class PlayPlaylist
"get_playlist_favorites", "get_playlist_favorites",
data: new SpotifyplusGetPlaylistFavoritesParameters data: new SpotifyplusGetPlaylistFavoritesParameters
{ {
Limit = 50, LimitTotal = 200,
EntityId = "media_player.spotifyplus",
SortResult = true, SortResult = true,
EntityId = Global.DEFAULT_ENTITY_ID,
} }
)).Value.Deserialize<SpotifyplusPlaylistResponse>(_jsonOptions); )).Value.Deserialize<SpotifyplusPlaylistResponse>(_jsonOptions);
@ -44,17 +45,15 @@ public class PlayPlaylist
if (query is not null && myPlaylists is not null) if (query is not null && myPlaylists is not null)
{ {
PlaylistsItem Query = new();
Query.Name = query.ToLower();
ExtractedResult<PlaylistsItem> match = Process.ExtractOne<PlaylistsItem>( ExtractedResult<PlaylistsItem> match = Process.ExtractOne<PlaylistsItem>(
Query, new PlaylistsItem { Name = query.ToLower() },
myPlaylists, myPlaylists,
new Func<PlaylistsItem, string>((item) => (item.Name ?? "").ToLower()) new Func<PlaylistsItem, string>((item) => (item.Name ?? "").ToLower())
); );
string uri = match.Value!.Uri!; string uri = match.Value!.Uri!;
// We search outside the user's playlists if the score is too low
if (match.Score < 85) if (match.Score < 85)
{ {
var otherResult = (await ha.CallServiceWithResponseAsync( var otherResult = (await ha.CallServiceWithResponseAsync(
@ -63,12 +62,20 @@ public class PlayPlaylist
data: new SpotifyplusSearchPlaylistsParameters data: new SpotifyplusSearchPlaylistsParameters
{ {
Criteria = query, Criteria = query,
Limit = 1, LimitTotal = 1,
EntityId = "media_player.spotifyplus", EntityId = Global.DEFAULT_ENTITY_ID,
// My Defaults
Market = "CA",
IncludeExternal = "audio",
} }
)).Value.Deserialize<SpotifyplusPlaylistResponse>(_jsonOptions); )).Value.Deserialize<SpotifyplusPlaylistResponse>(_jsonOptions);
uri = otherResult!.Result!.Items![0]!.Uri!; string? potentialUri = otherResult?.Result?.Items?[0]?.Uri;
if (potentialUri is not null)
{
uri = potentialUri;
}
} }
ha.CallService( ha.CallService(
@ -77,7 +84,11 @@ public class PlayPlaylist
data: new SpotifyplusPlayerMediaPlayContextParameters data: new SpotifyplusPlayerMediaPlayContextParameters
{ {
ContextUri = uri, ContextUri = uri,
EntityId = "media_player.spotifyplus" EntityId = Global.DEFAULT_ENTITY_ID,
DeviceId = Global.DEFAULT_DEV_ID,
// My Defaults
PositionMs = 0,
Delay = 0.50,
} }
); );
} }

View file

@ -2,9 +2,10 @@ namespace Spotify;
using System.Collections.Generic; using System.Collections.Generic;
public class PlaylistsItem
public record PlaylistsItem
{ {
public bool Collaborative { get; set; } public bool? Collaborative { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public ExternalUrls? ExternalUrls { get; set; } public ExternalUrls? ExternalUrls { get; set; }
public string? Href { get; set; } public string? Href { get; set; }
@ -23,15 +24,15 @@ public class PlaylistsItem
public record PlaylistsResult public record PlaylistsResult
{ {
public string? Href { get; set; } public string? Href { get; set; }
public int Limit { get; set; } public int? Limit { get; set; }
public object? Next { get; set; } public object? Next { get; set; }
public int Offset { get; set; } public int? Offset { get; set; }
public object? Previous { get; set; } public object? Previous { get; set; }
public int Total { get; set; } public int? Total { get; set; }
public List<PlaylistsItem>? Items { get; set; } public List<PlaylistsItem>? Items { get; set; }
} }
public class SpotifyplusPlaylistResponse public record SpotifyplusPlaylistResponse
{ {
public UserProfile? UserProfile { get; set; } public UserProfile? UserProfile { get; set; }
public PlaylistsResult? Result { get; set; } public PlaylistsResult? Result { get; set; }

View file

@ -0,0 +1,61 @@
namespace Spotify;
using HomeAssistantGenerated;
using NetDaemon.AppModel;
using NetDaemon.HassModel;
using NetDaemon.HassModel.Integration;
using System.Text.Json;
record PlaySongData(string? artist, string? song);
[NetDaemonApp]
public class PlaySong
{
// Snake-case json options
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
public PlaySong(IHaContext ha)
{
ha.RegisterServiceCallBack<PlaySongData>(
"spotify_play_song",
async (e) =>
{
var result = (await ha.CallServiceWithResponseAsync(
"spotifyplus",
"search_tracks",
data: new SpotifyplusSearchTracksParameters
{
Criteria = $"{e?.artist} {e?.song}",
LimitTotal = 1,
EntityId = Global.DEFAULT_ENTITY_ID,
// My Defaults
Market = "CA",
IncludeExternal = "audio",
}
)).Value.Deserialize<SpotifyplusSearchTracksResponse>(_jsonOptions);
string? uri = result?.Result?.Items?[0]?.Uri;
if (uri is not null)
{
ha.CallService(
"spotifyplus",
"player_media_play_tracks",
data: new SpotifyplusPlayerMediaPlayTracksParameters
{
Uris = uri,
EntityId = Global.DEFAULT_ENTITY_ID,
DeviceId = Global.DEFAULT_DEV_ID,
// My Defaults
PositionMs = 0,
Delay = 0.50,
}
);
}
}
);
}
}

View file

@ -0,0 +1,44 @@
namespace Spotify;
using System.Collections.Generic;
public record SongItem
{
public Album? Album { get; set; }
public List<Artist>? Artists { get; set; }
public List<string>? AvailableMarkets { get; set; }
public int? DiscNumber { get; set; }
public int? DurationMs { get; set; }
public bool? Explicit { get; set; }
public ExternalIds? ExternalIds { get; set; }
public ExternalUrls? ExternalUrls { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? ImageUrl { get; set; }
public bool? IsLocal { get; set; }
public object? IsPlayable { get; set; }
public string? Name { get; set; }
public int? Popularity { get; set; }
public object? PreviewUrl { get; set; }
public Restrictions? Restrictions { get; set; }
public int? TrackNumber { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record SongResult
{
public string? Href { get; set; }
public int? Limit { get; set; }
public string? Next { get; set; }
public int? Offset { get; set; }
public object? Previous { get; set; }
public int? Total { get; set; }
public List<SongItem>? Items { get; set; }
}
public record SpotifyplusSearchTracksResponse
{
public UserProfile? UserProfile { get; set; }
public SongResult? Result { get; set; }
}

View file

@ -1,14 +1,61 @@
namespace Spotify; namespace Spotify;
using System.Collections.Generic;
public static class Global
{
public const string DEFAULT_DEV_ID = "homie connect";
public const string DEFAULT_ENTITY_ID = "media_player.spotifyplus";
}
public record Restrictions { }
public record UserProfile public record UserProfile
{ {
public string? Country { get; init; } public string? Country { get; set; }
public string? DisplayName { get; init; } public string? DisplayName { get; set; }
public string? Email { get; init; } public string? Email { get; set; }
public string? Id { get; init; } public string? Id { get; set; }
public string? Product { get; init; } public string? Product { get; set; }
public string? Type { get; init; } public string? Type { get; set; }
public string? Uri { get; init; } public string? Uri { get; set; }
}
public record ExternalUrls
{
public string? Spotify { get; set; }
}
public record Followers
{
public string? Href { get; set; }
public int? Total { get; set; }
}
public record Image
{
public string? Url { get; set; }
public int? Height { get; set; }
public int? Width { get; set; }
}
public record Owner
{
public string? DisplayName { get; set; }
public ExternalUrls? ExternalUrls { get; set; }
public Followers? Followers { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record Tracks
{
public string? Href { get; set; }
public int? Total { get; set; }
} }
public record Artist public record Artist
@ -21,39 +68,33 @@ public record Artist
public string? Uri { get; set; } public string? Uri { get; set; }
} }
public record ExternalUrls public record ExternalIds
{ {
public string? Spotify { get; init; } public object? Ean { get; set; }
public string? Isrc { get; set; }
public object? Upc { get; set; }
} }
public record Followers public record Album
{ {
public string? Href { get; init; } public string? AlbumType { get; set; }
public int Total { get; init; } public List<Artist>? Artists { get; set; }
} public List<string>? AvailableMarkets { get; set; }
public List<object>? Copyrights { get; set; }
public record Image public ExternalIds? ExternalIds { get; set; }
{
public string? Url { get; init; }
public int? Height { get; init; }
public int? Width { get; init; }
}
public class Owner
{
public string? DisplayName { get; set; }
public ExternalUrls? ExternalUrls { get; set; } public ExternalUrls? ExternalUrls { get; set; }
public Followers? Followers { get; set; } public List<object>? genres { get; set; }
public string? Href { get; set; } public string? Href { get; set; }
public string? Id { get; set; } public string? Id { get; set; }
public string? ImageUrl { get; set; }
public List<Image>? Images { get; set; }
public object? Label { get; set; }
public string? Name { get; set; }
public object? Popularity { get; set; }
public string? ReleaseDate { get; set; }
public string? ReleaseDatePrecision { get; set; }
public Restrictions? Restrictions { get; set; }
public int? TotalTracks { get; set; }
public string? Type { get; set; } public string? Type { get; set; }
public string? Uri { get; set; } public string? Uri { get; set; }
} }
public class Tracks
{
public string? Href { get; set; }
public int Total { get; set; }
}
public record Restrictions { }

View file

@ -28,16 +28,31 @@
PlayPlaylist.data = [ PlayPlaylist.data = [
{ {
sentences = [ sentences = [
"play[ing] [the] playlist {playlist}" "play[ing] [(the|my)] playlist {playlist}"
]; ];
} }
]; ];
PlaySong.data = [
{
sentences = [
"play[ing] [the] (song|track) {song} from [the] [artist] {artist}"
];
}
{
sentences = [
"play[ing] [the] (song|track) {song}"
];
slots.artist = "";
}
];
}; };
lists = { lists = {
album.wildcard = true; album.wildcard = true;
artist.wildcard = true; artist.wildcard = true;
playlist.wildcard = true; playlist.wildcard = true;
song.wildcard = true;
}; };
responses.intents = { responses.intents = {
@ -56,5 +71,13 @@
PlayPlaylist.default = '' PlayPlaylist.default = ''
Searching for {{ slots.playlist }} in your favorites, or elsewhere if not found, and playing it. Searching for {{ slots.playlist }} in your favorites, or elsewhere if not found, and playing it.
''; '';
PlaySong.default = ''
Searching for the song {{ slots.song }}
{% if slots.artist != "" %}
by {{ slots.artist }}
{% endif %}
on Spotify and playing it.
'';
}; };
} }

View file

@ -67,6 +67,19 @@ in {
} }
]; ];
}; };
PlaySong = {
async_action = "false";
action = [
{
service = "netdaemon.spotify_play_song";
data = {
artist = "{{ artist }}";
song = "{{ song }}";
};
}
];
};
}; };
}; };
} }