feat(hass): add PlaySong voice command and some netd fixes
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
a4d2b936d9
commit
0f109a9a69
12 changed files with 280 additions and 67 deletions
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -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; }
|
||||
}
|
|
@ -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 { }
|
||||
|
|
|
@ -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.
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -67,6 +67,19 @@ in {
|
|||
}
|
||||
];
|
||||
};
|
||||
|
||||
PlaySong = {
|
||||
async_action = "false";
|
||||
action = [
|
||||
{
|
||||
service = "netdaemon.spotify_play_song";
|
||||
data = {
|
||||
artist = "{{ artist }}";
|
||||
song = "{{ song }}";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue