From 0f109a9a694d442588950015230601b1200829c9 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Wed, 9 Oct 2024 11:01:53 -0400 Subject: [PATCH] feat(hass): add PlaySong voice command and some netd fixes --- .../apps/Spotify/PlayAlbum/PlayAlbum.cs | 14 ++- .../SpotifyplusSearchAlbumsResponse.cs | 13 ++- .../apps/Spotify/PlayArtist/PlayArtist.cs | 14 ++- .../SpotifyplusSearchArtistsResponse.cs | 11 +- .../apps/Spotify/PlayPlaylist/PlayPlaylist.cs | 31 +++-- .../SpotifyplusPlaylistResponse.cs | 13 ++- .../apps/Spotify/PlaySong/PlaySong.cs | 61 ++++++++++ .../apps/Spotify/PlaySong/PlaySong.yaml | 1 + .../SpotifyplusSearchTracksResponse.cs | 44 +++++++ .../netdaemon/apps/Spotify/Types.cs | 107 ++++++++++++------ .../home-assistant/spotify-sentences.nix | 25 +++- .../homie/modules/home-assistant/spotify.nix | 13 +++ 12 files changed, 280 insertions(+), 67 deletions(-) create mode 100644 devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/PlaySong.cs create mode 100644 devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/PlaySong.yaml create mode 100644 devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/SpotifyplusSearchTracksResponse.cs diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayAlbum/PlayAlbum.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayAlbum/PlayAlbum.cs index ce8fd77f..a4d21c38 100644 --- a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayAlbum/PlayAlbum.cs +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayAlbum/PlayAlbum.cs @@ -8,6 +8,7 @@ using System.Text.Json; record PlayAlbumData(string? artist, string? album); + [NetDaemonApp] public class PlayAlbum { @@ -29,8 +30,11 @@ public class PlayAlbum data: new SpotifyplusSearchAlbumsParameters { Criteria = $"{e?.artist} {e?.album}", - Limit = 1, - EntityId = "media_player.spotifyplus" + LimitTotal = 1, + EntityId = Global.DEFAULT_ENTITY_ID, + // My Defaults + Market = "CA", + IncludeExternal = "audio", } )).Value.Deserialize(_jsonOptions); @@ -44,7 +48,11 @@ public class PlayAlbum data: new SpotifyplusPlayerMediaPlayContextParameters { ContextUri = uri, - EntityId = "media_player.spotifyplus" + EntityId = Global.DEFAULT_ENTITY_ID, + DeviceId = Global.DEFAULT_DEV_ID, + // My Defaults + PositionMs = 0, + Delay = 0.50, } ); } diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayAlbum/SpotifyplusSearchAlbumsResponse.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayAlbum/SpotifyplusSearchAlbumsResponse.cs index 49d79951..b96fe0a5 100644 --- a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayAlbum/SpotifyplusSearchAlbumsResponse.cs +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayAlbum/SpotifyplusSearchAlbumsResponse.cs @@ -2,11 +2,12 @@ namespace Spotify; using System.Collections.Generic; + public record AlbumItem { public string? AlbumType { get; set; } public List? Artists { get; set; } - public List? AvailableMarkets { get; set; } + public List? AvailableMarkets { get; set; } public ExternalUrls? ExternalUrls { get; set; } public string? Href { get; set; } public string? Id { get; set; } @@ -16,7 +17,7 @@ public record AlbumItem public string? ReleaseDate { get; set; } public string? ReleaseDatePrecision { get; set; } public Restrictions? Restrictions { get; set; } - public int TotalTracks { get; set; } + public int? TotalTracks { get; set; } public string? Type { get; set; } public string? Uri { get; set; } } @@ -24,15 +25,15 @@ public record AlbumItem public record AlbumResult { public string? Href { get; set; } - public int Limit { get; set; } + public int? Limit { get; set; } public string? Next { get; set; } - public int Offset { get; set; } + public int? Offset { get; set; } public object? Previous { get; set; } - public int Total { get; set; } + public int? Total { get; set; } public List? Items { get; set; } } -public class SpotifyplusSearchAlbumsResponse +public record SpotifyplusSearchAlbumsResponse { public UserProfile? UserProfile { get; set; } public AlbumResult? Result { get; set; } diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayArtist/PlayArtist.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayArtist/PlayArtist.cs index 18756922..86d2cccb 100644 --- a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayArtist/PlayArtist.cs +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayArtist/PlayArtist.cs @@ -8,6 +8,7 @@ using System.Text.Json; record PlayArtistData(string? artist); + [NetDaemonApp] public class PlayArtist { @@ -29,8 +30,11 @@ public class PlayArtist data: new SpotifyplusSearchArtistsParameters { Criteria = e?.artist, - Limit = 1, - EntityId = "media_player.spotifyplus" + LimitTotal = 1, + EntityId = Global.DEFAULT_ENTITY_ID, + // My Defaults + Market = "CA", + IncludeExternal = "audio", } )).Value.Deserialize(_jsonOptions); @@ -44,7 +48,11 @@ public class PlayArtist data: new SpotifyplusPlayerMediaPlayContextParameters { ContextUri = uri, - EntityId = "media_player.spotifyplus" + EntityId = Global.DEFAULT_ENTITY_ID, + DeviceId = Global.DEFAULT_DEV_ID, + // My Defaults + PositionMs = 0, + Delay = 0.50, } ); } diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayArtist/SpotifyplusSearchArtistsResponse.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayArtist/SpotifyplusSearchArtistsResponse.cs index 18e92f54..d4bc12cf 100644 --- a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayArtist/SpotifyplusSearchArtistsResponse.cs +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayArtist/SpotifyplusSearchArtistsResponse.cs @@ -2,17 +2,18 @@ namespace Spotify; using System.Collections.Generic; + public record ArtistItem { public ExternalUrls? External_urls { get; init; } public Followers? Followers { get; init; } - public List? Genres { get; init; } + public List? Genres { get; init; } public string? Href { get; init; } public string? Id { get; init; } public string? Image_url { get; init; } public List? Images { get; init; } public string? Name { get; init; } - public int Popularity { get; init; } + public int? Popularity { get; init; } public string? Type { get; init; } public string? Uri { get; init; } } @@ -20,11 +21,11 @@ public record ArtistItem public record ArtistResult { public string? Href { get; init; } - public int Limit { get; init; } + public int? Limit { get; init; } public string? Next { get; init; } - public int Offset { get; set; } + public int? Offset { get; set; } public object? Previous { get; init; } - public int Total { get; init; } + public int? Total { get; init; } public List? Items { get; init; } } diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.cs index 16a7b34a..968e1427 100644 --- a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.cs +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.cs @@ -12,6 +12,7 @@ using System.Text.Json; record PlayPlaylistData(string? playlist); + [NetDaemonApp] public class PlayPlaylist { @@ -34,9 +35,9 @@ public class PlayPlaylist "get_playlist_favorites", data: new SpotifyplusGetPlaylistFavoritesParameters { - Limit = 50, - EntityId = "media_player.spotifyplus", + LimitTotal = 200, SortResult = true, + EntityId = Global.DEFAULT_ENTITY_ID, } )).Value.Deserialize(_jsonOptions); @@ -44,17 +45,15 @@ public class PlayPlaylist if (query is not null && myPlaylists is not null) { - PlaylistsItem Query = new(); - Query.Name = query.ToLower(); - ExtractedResult match = Process.ExtractOne( - Query, + new PlaylistsItem { Name = query.ToLower() }, myPlaylists, new Func((item) => (item.Name ?? "").ToLower()) ); string uri = match.Value!.Uri!; + // We search outside the user's playlists if the score is too low if (match.Score < 85) { var otherResult = (await ha.CallServiceWithResponseAsync( @@ -63,12 +62,20 @@ public class PlayPlaylist data: new SpotifyplusSearchPlaylistsParameters { Criteria = query, - Limit = 1, - EntityId = "media_player.spotifyplus", + LimitTotal = 1, + EntityId = Global.DEFAULT_ENTITY_ID, + // My Defaults + Market = "CA", + IncludeExternal = "audio", } )).Value.Deserialize(_jsonOptions); - uri = otherResult!.Result!.Items![0]!.Uri!; + string? potentialUri = otherResult?.Result?.Items?[0]?.Uri; + + if (potentialUri is not null) + { + uri = potentialUri; + } } ha.CallService( @@ -77,7 +84,11 @@ public class PlayPlaylist data: new SpotifyplusPlayerMediaPlayContextParameters { ContextUri = uri, - EntityId = "media_player.spotifyplus" + EntityId = Global.DEFAULT_ENTITY_ID, + DeviceId = Global.DEFAULT_DEV_ID, + // My Defaults + PositionMs = 0, + Delay = 0.50, } ); } diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/SpotifyplusPlaylistResponse.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/SpotifyplusPlaylistResponse.cs index fa300b45..e433e06c 100644 --- a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/SpotifyplusPlaylistResponse.cs +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/SpotifyplusPlaylistResponse.cs @@ -2,9 +2,10 @@ namespace Spotify; 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 ExternalUrls? ExternalUrls { get; set; } public string? Href { get; set; } @@ -23,15 +24,15 @@ public class PlaylistsItem public record PlaylistsResult { public string? Href { get; set; } - public int Limit { get; set; } + public int? Limit { get; set; } public object? Next { get; set; } - public int Offset { get; set; } + public int? Offset { get; set; } public object? Previous { get; set; } - public int Total { get; set; } + public int? Total { get; set; } public List? Items { get; set; } } -public class SpotifyplusPlaylistResponse +public record SpotifyplusPlaylistResponse { public UserProfile? UserProfile { get; set; } public PlaylistsResult? Result { get; set; } diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/PlaySong.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/PlaySong.cs new file mode 100644 index 00000000..450fa723 --- /dev/null +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/PlaySong.cs @@ -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( + "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(_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, + } + ); + } + } + ); + } +} diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/PlaySong.yaml b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/PlaySong.yaml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/PlaySong.yaml @@ -0,0 +1 @@ + diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/SpotifyplusSearchTracksResponse.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/SpotifyplusSearchTracksResponse.cs new file mode 100644 index 00000000..64b08b5e --- /dev/null +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlaySong/SpotifyplusSearchTracksResponse.cs @@ -0,0 +1,44 @@ +namespace Spotify; + +using System.Collections.Generic; + +public record SongItem +{ + public Album? Album { get; set; } + public List? Artists { get; set; } + public List? 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? Items { get; set; } +} + +public record SpotifyplusSearchTracksResponse +{ + public UserProfile? UserProfile { get; set; } + public SongResult? Result { get; set; } +} diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/Types.cs b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/Types.cs index d3c62673..269cf91a 100644 --- a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/Types.cs +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/Types.cs @@ -1,14 +1,61 @@ 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 string? Country { get; init; } - public string? DisplayName { get; init; } - public string? Email { get; init; } - public string? Id { get; init; } - public string? Product { get; init; } - public string? Type { get; init; } - public string? Uri { get; init; } + public string? Country { get; set; } + public string? DisplayName { get; set; } + public string? Email { get; set; } + public string? Id { get; set; } + public string? Product { get; set; } + public string? Type { get; set; } + 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 @@ -21,39 +68,33 @@ public record Artist 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 int Total { get; init; } -} - -public record Image -{ - public string? Url { get; init; } - public int? Height { get; init; } - public int? Width { get; init; } -} - -public class Owner -{ - public string? DisplayName { get; set; } + public string? AlbumType { get; set; } + public List? Artists { get; set; } + public List? AvailableMarkets { get; set; } + public List? Copyrights { get; set; } + public ExternalIds? ExternalIds { get; set; } public ExternalUrls? ExternalUrls { get; set; } - public Followers? Followers { get; set; } + public List? genres { get; set; } public string? Href { get; set; } public string? Id { get; set; } + public string? ImageUrl { get; set; } + public List? 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? Uri { get; set; } } - -public class Tracks -{ - public string? Href { get; set; } - public int Total { get; set; } -} - -public record Restrictions { } diff --git a/devices/homie/modules/home-assistant/spotify-sentences.nix b/devices/homie/modules/home-assistant/spotify-sentences.nix index 95520ead..cd7d1c14 100644 --- a/devices/homie/modules/home-assistant/spotify-sentences.nix +++ b/devices/homie/modules/home-assistant/spotify-sentences.nix @@ -28,16 +28,31 @@ PlayPlaylist.data = [ { 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 = { album.wildcard = true; artist.wildcard = true; playlist.wildcard = true; + song.wildcard = true; }; responses.intents = { @@ -56,5 +71,13 @@ PlayPlaylist.default = '' 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. + ''; }; } diff --git a/devices/homie/modules/home-assistant/spotify.nix b/devices/homie/modules/home-assistant/spotify.nix index d7b4dee4..f6e08562 100644 --- a/devices/homie/modules/home-assistant/spotify.nix +++ b/devices/homie/modules/home-assistant/spotify.nix @@ -67,6 +67,19 @@ in { } ]; }; + + PlaySong = { + async_action = "false"; + action = [ + { + service = "netdaemon.spotify_play_song"; + data = { + artist = "{{ artist }}"; + song = "{{ song }}"; + }; + } + ]; + }; }; }; }