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);
[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<SpotifyplusSearchAlbumsResponse>(_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,
}
);
}

View file

@ -2,11 +2,12 @@ namespace Spotify;
using System.Collections.Generic;
public record AlbumItem
{
public string? AlbumType { 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 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<AlbumItem>? Items { get; set; }
}
public class SpotifyplusSearchAlbumsResponse
public record SpotifyplusSearchAlbumsResponse
{
public UserProfile? UserProfile { get; set; }
public AlbumResult? Result { get; set; }

View file

@ -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<SpotifyplusSearchArtistsResponse>(_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,
}
);
}

View file

@ -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<string?>? Genres { get; init; }
public List<string>? Genres { get; init; }
public string? Href { get; init; }
public string? Id { get; init; }
public string? Image_url { get; init; }
public List<Image>? 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<ArtistItem>? Items { get; init; }
}

View file

@ -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<SpotifyplusPlaylistResponse>(_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<PlaylistsItem> match = Process.ExtractOne<PlaylistsItem>(
Query,
new PlaylistsItem { Name = query.ToLower() },
myPlaylists,
new Func<PlaylistsItem, string>((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<SpotifyplusPlaylistResponse>(_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,
}
);
}

View file

@ -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<PlaylistsItem>? Items { get; set; }
}
public class SpotifyplusPlaylistResponse
public record SpotifyplusPlaylistResponse
{
public UserProfile? UserProfile { 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;
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<Artist>? Artists { get; set; }
public List<string>? AvailableMarkets { get; set; }
public List<object>? Copyrights { get; set; }
public ExternalIds? ExternalIds { 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? 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? 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 = [
{
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.
'';
};
}

View file

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