From a4d2b936d933fb6acaa81b7ac744373ccfe69a57 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Wed, 9 Oct 2024 00:34:58 -0400 Subject: [PATCH] feat(hass): add PlayPlaylist voice command --- .../netdaemon/HomeAssistantGenerated.cs | 273 +++++++++++++++--- .../apps/Spotify/PlayPlaylist/PlayPlaylist.cs | 87 ++++++ .../Spotify/PlayPlaylist/PlayPlaylist.yaml | 1 + .../SpotifyplusPlaylistResponse.cs | 38 +++ .../netdaemon/apps/Spotify/Types.cs | 21 +- .../home-assistant/netdaemon/default.nix | 2 +- .../modules/home-assistant/netdaemon/deps.nix | 5 + .../home-assistant/netdaemon/netdaemon.csproj | 1 + .../home-assistant/spotify-sentences.nix | 41 ++- .../homie/modules/home-assistant/spotify.nix | 30 +- flake.lock | Bin 57719 -> 57232 bytes flake.nix | Bin 11334 -> 11208 bytes inputs.nix | 4 - outputs.nix | 1 - 14 files changed, 433 insertions(+), 71 deletions(-) create mode 100644 devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.cs create mode 100644 devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.yaml create mode 100644 devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/SpotifyplusPlaylistResponse.cs diff --git a/devices/homie/modules/home-assistant/netdaemon/HomeAssistantGenerated.cs b/devices/homie/modules/home-assistant/netdaemon/HomeAssistantGenerated.cs index b5bd7153..7923695a 100644 --- a/devices/homie/modules/home-assistant/netdaemon/HomeAssistantGenerated.cs +++ b/devices/homie/modules/home-assistant/netdaemon/HomeAssistantGenerated.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // Generated using NetDaemon CodeGenerator nd-codegen v24.37.1.0 -// At: 2024-10-05T16:11:38.2552699-04:00 +// At: 2024-10-08T21:14:58.2735702-04:00 // // *** Make sure the version of the codegen tool and your nugets Joysoftware.NetDaemon.* have the same version.*** // You can use following command to keep it up to date with the latest version: @@ -82,6 +82,7 @@ public static class GeneratedExtensions serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); @@ -105,6 +106,7 @@ public static class GeneratedExtensions serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); @@ -271,8 +273,6 @@ public partial class BinarySensorEntities public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("binary_sensor.")).Select(e => new BinarySensorEntity(e)); ///CODA-4680-FIZ WAN status public BinarySensorEntity Coda4680FizWanStatus => new(_haContext, "binary_sensor.coda_4680_fiz_wan_status"); - ///M5Stack Atom Echo 31196c Assist in progress - public BinarySensorEntity M5stackAtomEcho131196cAssistInProgress => new(_haContext, "binary_sensor.m5stack_atom_echo_1_31196c_assist_in_progress"); ///M5Stack Atom Echo 31196c Button public BinarySensorEntity M5stackAtomEcho131196cButton => new(_haContext, "binary_sensor.m5stack_atom_echo_1_31196c_button"); ///Pixel 8 Bluetooth state @@ -343,6 +343,8 @@ public partial class ConversationEntities /// Enumerates all conversation entities currently registered (at runtime) in Home Assistant as ConversationEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("conversation.")).Select(e => new ConversationEntity(e)); + ///Fallback Conversation Agent + public ConversationEntity FallbackConversationAgent => new(_haContext, "conversation.fallback_conversation_agent"); ///Home Assistant public ConversationEntity HomeAssistant => new(_haContext, "conversation.home_assistant"); } @@ -359,6 +361,8 @@ public partial class DeviceTrackerEntities public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("device_tracker.")).Select(e => new DeviceTrackerEntity(e)); ///Pixel 8 public DeviceTrackerEntity Pixel8 => new(_haContext, "device_tracker.pixel_8"); + ///Z Flip 6 + public DeviceTrackerEntity SmF741w => new(_haContext, "device_tracker.sm_f741w"); } public partial class InputBooleanEntities @@ -371,8 +375,10 @@ public partial class InputBooleanEntities /// Enumerates all input_boolean entities currently registered (at runtime) in Home Assistant as InputBooleanEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("input_boolean.")).Select(e => new InputBooleanEntity(e)); - ///netdaemon_app_model_test_script - public InputBooleanEntity NetdaemonAppModelTestScript => new(_haContext, "input_boolean.netdaemon_app_model_test_script"); + ///netdaemon_spotify_play_album + public InputBooleanEntity NetdaemonSpotifyPlayAlbum => new(_haContext, "input_boolean.netdaemon_spotify_play_album"); + ///netdaemon_spotify_play_artist + public InputBooleanEntity NetdaemonSpotifyPlayArtist => new(_haContext, "input_boolean.netdaemon_spotify_play_artist"); } public partial class InputTextEntities @@ -403,7 +409,7 @@ public partial class LockEntities /// Enumerates all lock entities currently registered (at runtime) in Home Assistant as LockEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("lock.")).Select(e => new LockEntity(e)); - ///Smartplug 1 Child lock + ///Smartplug1 Child lock public LockEntity Smartplug1ChildLock => new(_haContext, "lock.smartplug_1_child_lock"); ///Smartplug2 Child lock public LockEntity Smartplug2ChildLock => new(_haContext, "lock.smartplug2_child_lock"); @@ -421,7 +427,7 @@ public partial class NumberEntities /// Enumerates all number entities currently registered (at runtime) in Home Assistant as NumberEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("number.")).Select(e => new NumberEntity(e)); - ///Smartplug 1 Timer + ///Smartplug1 Timer public NumberEntity Smartplug1Timer => new(_haContext, "number.smartplug_1_timer"); ///Smartplug2 Timer public NumberEntity Smartplug2Timer => new(_haContext, "number.smartplug2_timer"); @@ -439,8 +445,10 @@ public partial class PersonEntities /// Enumerates all person entities currently registered (at runtime) in Home Assistant as PersonEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("person.")).Select(e => new PersonEntity(e)); - ///Mathis Hurtubise - public PersonEntity MathisHurtubise => new(_haContext, "person.mathis_hurtubise"); + ///Matt + public PersonEntity Matt => new(_haContext, "person.matt"); + ///Wifey <3 + public PersonEntity Wifey => new(_haContext, "person.wifey"); } public partial class RemoteEntities @@ -473,8 +481,6 @@ public partial class ScriptEntities public ScriptEntity AssistTimerstart => new(_haContext, "script.assist_timerstart"); ///Assist - TimerStop public ScriptEntity AssistTimerstop => new(_haContext, "script.assist_timerstop"); - ///Spotify - Play Artist - public ScriptEntity PlayArtist => new(_haContext, "script.play_artist"); ///Music - TurnOnUE public ScriptEntity TurnOnUe => new(_haContext, "script.turn_on_ue"); } @@ -489,15 +495,15 @@ public partial class SelectEntities /// Enumerates all select entities currently registered (at runtime) in Home Assistant as SelectEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("select.")).Select(e => new SelectEntity(e)); - ///M5Stack Atom Echo 31196c Assist pipeline + ///M5Stack Atom Echo 31196c Assistant public SelectEntity M5stackAtomEcho131196cAssistPipeline => new(_haContext, "select.m5stack_atom_echo_1_31196c_assist_pipeline"); ///M5Stack Atom Echo 31196c Finished speaking detection public SelectEntity M5stackAtomEcho131196cFinishedSpeakingDetection => new(_haContext, "select.m5stack_atom_echo_1_31196c_finished_speaking_detection"); ///M5Stack Atom Echo 31196c Wake word engine location public SelectEntity M5stackAtomEcho131196cWakeWordEngineLocation => new(_haContext, "select.m5stack_atom_echo_1_31196c_wake_word_engine_location"); - ///Smartplug 1 Initial state + ///Smartplug1 Initial state public SelectEntity Smartplug1InitialState => new(_haContext, "select.smartplug_1_initial_state"); - ///Smartplug 1 Light mode + ///Smartplug1 Light mode public SelectEntity Smartplug1LightMode => new(_haContext, "select.smartplug_1_light_mode"); ///Smartplug2 Initial state public SelectEntity Smartplug2InitialState => new(_haContext, "select.smartplug2_initial_state"); @@ -525,6 +531,10 @@ public partial class SensorEntities public IEnumerable EnumerateAllNumeric() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("sensor.") && (e.EntityState?.AttributesJson?.TryGetProperty("unit_of_measurement", out _) ?? false)).Select(e => new NumericSensorEntity(e)); ///CODA-4680-FIZ External IP public SensorEntity Coda4680FizExternalIp => new(_haContext, "sensor.coda_4680_fiz_external_ip"); + ///Material Rounded Base Color Matt + public SensorEntity MaterialRoundedBaseColorMatt => new(_haContext, "sensor.material_rounded_base_color_matt"); + ///Pixel 8 Accent color + public SensorEntity Pixel8AccentColor => new(_haContext, "sensor.pixel_8_accent_color"); ///Pixel 8 Audio mode public SensorEntity Pixel8AudioMode => new(_haContext, "sensor.pixel_8_audio_mode"); ///Pixel 8 Battery health @@ -587,6 +597,10 @@ public partial class SensorEntities public SensorEntity Pixel8WifiConnection => new(_haContext, "sensor.pixel_8_wifi_connection"); ///Pixel 8 WiFi IP address public SensorEntity Pixel8WifiIpAddress => new(_haContext, "sensor.pixel_8_wifi_ip_address"); + ///SM-F741W Battery state + public SensorEntity SmF741wBatteryState => new(_haContext, "sensor.sm_f741w_battery_state"); + ///SM-F741W Charger type + public SensorEntity SmF741wChargerType => new(_haContext, "sensor.sm_f741w_charger_type"); ///Sun Next dawn public SensorEntity SunNextDawn => new(_haContext, "sensor.sun_next_dawn"); ///Sun Next dusk @@ -599,6 +613,10 @@ public partial class SensorEntities public SensorEntity SunNextRising => new(_haContext, "sensor.sun_next_rising"); ///Sun Next setting public SensorEntity SunNextSetting => new(_haContext, "sensor.sun_next_setting"); + ///CODA-4680-FIZ Data received + public NumericSensorEntity Coda4680FizDataReceived => new(_haContext, "sensor.coda_4680_fiz_data_received"); + ///CODA-4680-FIZ Data sent + public NumericSensorEntity Coda4680FizDataSent => new(_haContext, "sensor.coda_4680_fiz_data_sent"); ///CODA-4680-FIZ Download speed public NumericSensorEntity Coda4680FizDownloadSpeed => new(_haContext, "sensor.coda_4680_fiz_download_speed"); ///CODA-4680-FIZ Upload speed @@ -623,6 +641,8 @@ public partial class SensorEntities public NumericSensorEntity Pixel8WifiLinkSpeed => new(_haContext, "sensor.pixel_8_wifi_link_speed"); ///Pixel 8 WiFi signal strength public NumericSensorEntity Pixel8WifiSignalStrength => new(_haContext, "sensor.pixel_8_wifi_signal_strength"); + ///SM-F741W Battery level + public NumericSensorEntity SmF741wBatteryLevel => new(_haContext, "sensor.sm_f741w_battery_level"); } public partial class SttEntities @@ -670,7 +690,7 @@ public partial class SwitchEntities ///Salon Lamp public SwitchEntity Smartplug1 => new(_haContext, "switch.smartplug1"); ///Smartplug2 None - public SwitchEntity Smartplug2None => new(_haContext, "switch.smartplug2_none"); + public SwitchEntity Smartplug2 => new(_haContext, "switch.smartplug2"); ///Office Lamp public SwitchEntity Smartplug3 => new(_haContext, "switch.smartplug3"); } @@ -1113,6 +1133,27 @@ public partial record DeviceTrackerAttributes [JsonPropertyName("friendly_name")] public string? FriendlyName { get; init; } + + [JsonPropertyName("latitude")] + public double? Latitude { get; init; } + + [JsonPropertyName("longitude")] + public double? Longitude { get; init; } + + [JsonPropertyName("gps_accuracy")] + public double? GpsAccuracy { get; init; } + + [JsonPropertyName("altitude")] + public double? Altitude { get; init; } + + [JsonPropertyName("course")] + public double? Course { get; init; } + + [JsonPropertyName("speed")] + public double? Speed { get; init; } + + [JsonPropertyName("vertical_accuracy")] + public double? VerticalAccuracy { get; init; } } public partial record InputBooleanEntity : Entity, InputBooleanAttributes>, IInputBooleanEntityCore @@ -1252,6 +1293,18 @@ public partial record PersonAttributes [JsonPropertyName("friendly_name")] public string? FriendlyName { get; init; } + + [JsonPropertyName("latitude")] + public double? Latitude { get; init; } + + [JsonPropertyName("longitude")] + public double? Longitude { get; init; } + + [JsonPropertyName("gps_accuracy")] + public double? GpsAccuracy { get; init; } + + [JsonPropertyName("source")] + public string? Source { get; init; } } public partial record RemoteEntity : Entity, RemoteAttributes>, IRemoteEntityCore @@ -1392,6 +1445,33 @@ public partial record SensorAttributes [JsonPropertyName("metered")] public bool? Metered { get; init; } + + [JsonPropertyName("rgb_color")] + public IReadOnlyList? RgbColor { get; init; } + + [JsonPropertyName("album_com.spotify.music")] + public string? AlbumCom_spotify_music { get; init; } + + [JsonPropertyName("artist_com.spotify.music")] + public string? ArtistCom_spotify_music { get; init; } + + [JsonPropertyName("duration_com.spotify.music")] + public double? DurationCom_spotify_music { get; init; } + + [JsonPropertyName("media_id_com.spotify.music")] + public string? MediaIdCom_spotify_music { get; init; } + + [JsonPropertyName("playback_position_com.spotify.music")] + public double? PlaybackPositionCom_spotify_music { get; init; } + + [JsonPropertyName("playback_state_com.spotify.music")] + public string? PlaybackStateCom_spotify_music { get; init; } + + [JsonPropertyName("title_com.spotify.music")] + public string? TitleCom_spotify_music { get; init; } + + [JsonPropertyName("total_media_session_count")] + public double? TotalMediaSessionCount { get; init; } } public partial record NumericSensorEntity : NumericEntity, NumericSensorAttributes>, ISensorEntityCore @@ -1671,6 +1751,9 @@ public partial record WeatherAttributes [JsonPropertyName("supported_features")] public double? SupportedFeatures { get; init; } + + [JsonPropertyName("uv_index")] + public double? UvIndex { get; init; } } public partial record ZoneEntity : Entity, ZoneAttributes>, IZoneEntityCore @@ -1699,7 +1782,7 @@ public partial record ZoneAttributes public bool? Passive { get; init; } [JsonPropertyName("persons")] - public IReadOnlyList? Persons { get; init; } + public object? Persons { get; init; } [JsonPropertyName("editable")] public bool? Editable { get; init; } @@ -1751,6 +1834,8 @@ public interface IServices InputTextServices InputText { get; } + IntentScriptServices IntentScript { get; } + LightServices Light { get; } LockServices Lock { get; } @@ -1797,6 +1882,8 @@ public interface IServices SystemLogServices SystemLog { get; } + TemplateServices Template { get; } + TimerServices Timer { get; } TodoServices Todo { get; } @@ -1835,6 +1922,7 @@ public partial class Services : IServices public InputNumberServices InputNumber => new(_haContext); public InputSelectServices InputSelect => new(_haContext); public InputTextServices InputText => new(_haContext); + public IntentScriptServices IntentScript => new(_haContext); public LightServices Light => new(_haContext); public LockServices Lock => new(_haContext); public LogbookServices Logbook => new(_haContext); @@ -1858,6 +1946,7 @@ public partial class Services : IServices public SpotifyplusServices Spotifyplus => new(_haContext); public SwitchServices Switch => new(_haContext); public SystemLogServices SystemLog => new(_haContext); + public TemplateServices Template => new(_haContext); public TimerServices Timer => new(_haContext); public TodoServices Todo => new(_haContext); public TtsServices Tts => new(_haContext); @@ -3132,6 +3221,21 @@ public partial record InputTextSetValueParameters public string? Value { get; init; } } +public partial class IntentScriptServices +{ + private readonly IHaContext _haContext; + public IntentScriptServices(IHaContext haContext) + { + _haContext = haContext; + } + + ///Reloads the intent script from the YAML-configuration. + public void Reload(object? data = null) + { + _haContext.CallService("intent_script", "reload", null, data); + } +} + public partial class LightServices { private readonly IHaContext _haContext; @@ -3825,11 +3929,6 @@ public partial class NetdaemonServices _haContext = haContext; } - public void CallbackDemo(object? data = null) - { - _haContext.CallService("netdaemon", "callback_demo", null, data); - } - ///Create an entity public void EntityCreate(NetdaemonEntityCreateParameters data) { @@ -3898,6 +3997,16 @@ public partial class NetdaemonServices { _haContext.CallService("netdaemon", "reload_apps", null, data); } + + public void SpotifyPlayAlbum(object? data = null) + { + _haContext.CallService("netdaemon", "spotify_play_album", null, data); + } + + public void SpotifyPlayArtist(object? data = null) + { + _haContext.CallService("netdaemon", "spotify_play_artist", null, data); + } } public partial record NetdaemonEntityCreateParameters @@ -4000,6 +4109,22 @@ public partial class NotifyServices _haContext.CallService("notify", "mobile_app_pixel_8", null, new NotifyMobileAppPixel8Parameters { Message = message, Title = title, Target = target, Data = data }); } + ///Sends a notification message using the mobile_app_z_flip_6 integration. + public void MobileAppZFlip6(NotifyMobileAppZFlip6Parameters data) + { + _haContext.CallService("notify", "mobile_app_z_flip_6", null, data); + } + + ///Sends a notification message using the mobile_app_z_flip_6 integration. + /// eg: The garage door has been open for 10 minutes. + /// eg: Your Garage Door Friend + /// eg: platform specific + /// eg: platform specific + public void MobileAppZFlip6(string message, string? title = null, object? target = null, object? data = null) + { + _haContext.CallService("notify", "mobile_app_z_flip_6", null, new NotifyMobileAppZFlip6Parameters { Message = message, Title = title, Target = target, Data = data }); + } + ///Sends a notification message using the notify service. public void Notify(NotifyNotifyParameters data) { @@ -4066,6 +4191,25 @@ public partial record NotifyMobileAppPixel8Parameters public object? Data { get; init; } } +public partial record NotifyMobileAppZFlip6Parameters +{ + /// eg: The garage door has been open for 10 minutes. + [JsonPropertyName("message")] + public string? Message { get; init; } + + /// eg: Your Garage Door Friend + [JsonPropertyName("title")] + public string? Title { get; init; } + + /// eg: platform specific + [JsonPropertyName("target")] + public object? Target { get; init; } + + /// eg: platform specific + [JsonPropertyName("data")] + public object? Data { get; init; } +} + public partial record NotifyNotifyParameters { /// eg: The garage door has been open for 10 minutes. @@ -4631,16 +4775,6 @@ public partial class ScriptServices return _haContext.CallServiceWithResponseAsync("script", "assist_timerstop", null, data); } - public void PlayArtist(object? data = null) - { - _haContext.CallService("script", "play_artist", null, data); - } - - public Task PlayArtistAsync(object? data = null) - { - return _haContext.CallServiceWithResponseAsync("script", "play_artist", null, data); - } - ///Reloads all the available scripts. public void Reload(object? data = null) { @@ -5840,9 +5974,10 @@ public partial class SpotifyplusServices ///The page index offset of the first item to return. Use with limit to get the next set of items. Default is 0 (the first item). eg: 0 ///The maximum number of items to return for the request. If specified, this argument overrides the limit and offset argument values and paging is automatically used to retrieve all available items up to the specified limit total. eg: 20 ///True to sort result items by name prior to returning to the caller; otherwise, False to return results in the order that the Spotify Web API returned them. eg: True - public void GetShowFavorites(string entityId, double? limit = null, double? offset = null, double? limitTotal = null, bool? sortResult = null) + ///True (default) to exclude audiobook shows from the returned list, leaving only podcast shows; otherwise, False to include all results returned by the Spotify Web API. eg: True + public void GetShowFavorites(string entityId, double? limit = null, double? offset = null, double? limitTotal = null, bool? sortResult = null, bool? excludeAudiobooks = null) { - _haContext.CallService("spotifyplus", "get_show_favorites", null, new SpotifyplusGetShowFavoritesParameters { EntityId = entityId, Limit = limit, Offset = offset, LimitTotal = limitTotal, SortResult = sortResult }); + _haContext.CallService("spotifyplus", "get_show_favorites", null, new SpotifyplusGetShowFavoritesParameters { EntityId = entityId, Limit = limit, Offset = offset, LimitTotal = limitTotal, SortResult = sortResult, ExcludeAudiobooks = excludeAudiobooks }); } ///Get a list of the shows saved in the current Spotify user's 'Your Library'. @@ -5857,9 +5992,10 @@ public partial class SpotifyplusServices ///The page index offset of the first item to return. Use with limit to get the next set of items. Default is 0 (the first item). eg: 0 ///The maximum number of items to return for the request. If specified, this argument overrides the limit and offset argument values and paging is automatically used to retrieve all available items up to the specified limit total. eg: 20 ///True to sort result items by name prior to returning to the caller; otherwise, False to return results in the order that the Spotify Web API returned them. eg: True - public Task GetShowFavoritesAsync(string entityId, double? limit = null, double? offset = null, double? limitTotal = null, bool? sortResult = null) + ///True (default) to exclude audiobook shows from the returned list, leaving only podcast shows; otherwise, False to include all results returned by the Spotify Web API. eg: True + public Task GetShowFavoritesAsync(string entityId, double? limit = null, double? offset = null, double? limitTotal = null, bool? sortResult = null, bool? excludeAudiobooks = null) { - return _haContext.CallServiceWithResponseAsync("spotifyplus", "get_show_favorites", null, new SpotifyplusGetShowFavoritesParameters { EntityId = entityId, Limit = limit, Offset = offset, LimitTotal = limitTotal, SortResult = sortResult }); + return _haContext.CallServiceWithResponseAsync("spotifyplus", "get_show_favorites", null, new SpotifyplusGetShowFavoritesParameters { EntityId = entityId, Limit = limit, Offset = offset, LimitTotal = limitTotal, SortResult = sortResult, ExcludeAudiobooks = excludeAudiobooks }); } ///Get information about a specific Spotify Connect player device, and (optionally) activate the device if it requires it. @@ -6178,13 +6314,30 @@ public partial class SpotifyplusServices _haContext.CallService("spotifyplus", "player_media_play_tracks", null, new SpotifyplusPlayerMediaPlayTracksParameters { EntityId = entityId, Uris = uris, PositionMs = positionMs, DeviceId = deviceId, Delay = delay }); } - ///Resolves a Spotify Connect device identifier from a specified device id, name, alias id, or alias name. This will ensure that the device id can be found on the network, as well as connect to the device if necessary with the current user context. + ///Seeks to the given absolute or relative position in the user's currently playing track for the specified Spotify Connect device. + public void PlayerMediaSeek(SpotifyplusPlayerMediaSeekParameters data) + { + _haContext.CallService("spotifyplus", "player_media_seek", null, data); + } + + ///Seeks to the given absolute or relative position in the user's currently playing track for the specified Spotify Connect device. + ///Entity ID of the SpotifyPlus device that will make the request to the Spotify Web API. eg: media_player.spotifyplus_username + ///The absolute position in milliseconds to seek to; must be a positive number or zero if the `relativePositionMS` argument is specified. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. Example = `25000` to start playing at the 25 second mark. eg: 25000 + ///The id or name of the Spotify Connect Player device this command is targeting. If not supplied, the user's currently active device is the target. If no device is active (or an '*' is specified), then the SpotifyPlus default device is activated. eg: 0d1841b0976bae2a3a310dd74c0f337465899bc8 + ///Time delay (in seconds) to wait AFTER issuing the final Connect command (if necessary). This delay will give the spotify web api time to process the device list change before another command is issued. Default is 0.50; value range is 0 - 10. eg: 0.50 + ///The relative position in milliseconds to seek to; can be a positive or negative number, or zero if the `positionMS` argument is specified. Example = `-10000` to seek behind by 10 seconds; `10000` to seek ahead by 10 seconds. eg: 10000 + public void PlayerMediaSeek(string entityId, double? positionMs = null, string? deviceId = null, double? delay = null, double? relativePositionMs = null) + { + _haContext.CallService("spotifyplus", "player_media_seek", null, new SpotifyplusPlayerMediaSeekParameters { EntityId = entityId, PositionMs = positionMs, DeviceId = deviceId, Delay = delay, RelativePositionMs = relativePositionMs }); + } + + ///Resolves a Spotify Connect device identifier from a specified device id, name, alias id, or alias name. This will ensure that the device id can be found on the network, as well as connect to the device if necessary with the current user context. public void PlayerResolveDeviceId(SpotifyplusPlayerResolveDeviceIdParameters data) { _haContext.CallService("spotifyplus", "player_resolve_device_id", null, data); } - ///Resolves a Spotify Connect device identifier from a specified device id, name, alias id, or alias name. This will ensure that the device id can be found on the network, as well as connect to the device if necessary with the current user context. + ///Resolves a Spotify Connect device identifier from a specified device id, name, alias id, or alias name. This will ensure that the device id can be found on the network, as well as connect to the device if necessary with the current user context. ///Entity ID of the SpotifyPlus device that will make the request to the Spotify Web API. eg: media_player.spotifyplus_username ///The device id (e.g. '0d1841b0976bae2a3a310dd74c0f337465899bc8') or name (e.g. 'Bose-ST10-1') value to resolve. eg: Bose-ST10-1 ///If True, the active user context of the resolved device is checked to ensure it matches the user context specified on the class constructor. If False, the user context will not be checked. Default is True. eg: True @@ -6194,13 +6347,13 @@ public partial class SpotifyplusServices _haContext.CallService("spotifyplus", "player_resolve_device_id", null, new SpotifyplusPlayerResolveDeviceIdParameters { EntityId = entityId, DeviceValue = deviceValue, VerifyUserContext = verifyUserContext, VerifyTimeout = verifyTimeout }); } - ///Resolves a Spotify Connect device identifier from a specified device id, name, alias id, or alias name. This will ensure that the device id can be found on the network, as well as connect to the device if necessary with the current user context. + ///Resolves a Spotify Connect device identifier from a specified device id, name, alias id, or alias name. This will ensure that the device id can be found on the network, as well as connect to the device if necessary with the current user context. public Task PlayerResolveDeviceIdAsync(SpotifyplusPlayerResolveDeviceIdParameters data) { return _haContext.CallServiceWithResponseAsync("spotifyplus", "player_resolve_device_id", null, data); } - ///Resolves a Spotify Connect device identifier from a specified device id, name, alias id, or alias name. This will ensure that the device id can be found on the network, as well as connect to the device if necessary with the current user context. + ///Resolves a Spotify Connect device identifier from a specified device id, name, alias id, or alias name. This will ensure that the device id can be found on the network, as well as connect to the device if necessary with the current user context. ///Entity ID of the SpotifyPlus device that will make the request to the Spotify Web API. eg: media_player.spotifyplus_username ///The device id (e.g. '0d1841b0976bae2a3a310dd74c0f337465899bc8') or name (e.g. 'Bose-ST10-1') value to resolve. eg: Bose-ST10-1 ///If True, the active user context of the resolved device is checked to ensure it matches the user context specified on the class constructor. If False, the user context will not be checked. Default is True. eg: True @@ -7560,6 +7713,10 @@ public partial record SpotifyplusGetShowFavoritesParameters ///True to sort result items by name prior to returning to the caller; otherwise, False to return results in the order that the Spotify Web API returned them. eg: True [JsonPropertyName("sort_result")] public bool? SortResult { get; init; } + + ///True (default) to exclude audiobook shows from the returned list, leaving only podcast shows; otherwise, False to include all results returned by the Spotify Web API. eg: True + [JsonPropertyName("exclude_audiobooks")] + public bool? ExcludeAudiobooks { get; init; } } public partial record SpotifyplusGetSpotifyConnectDeviceParameters @@ -7807,6 +7964,29 @@ public partial record SpotifyplusPlayerMediaPlayTracksParameters public double? Delay { get; init; } } +public partial record SpotifyplusPlayerMediaSeekParameters +{ + ///Entity ID of the SpotifyPlus device that will make the request to the Spotify Web API. eg: media_player.spotifyplus_username + [JsonPropertyName("entity_id")] + public string? EntityId { get; init; } + + ///The absolute position in milliseconds to seek to; must be a positive number or zero if the `relativePositionMS` argument is specified. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. Example = `25000` to start playing at the 25 second mark. eg: 25000 + [JsonPropertyName("position_ms")] + public double? PositionMs { get; init; } + + ///The id or name of the Spotify Connect Player device this command is targeting. If not supplied, the user's currently active device is the target. If no device is active (or an '*' is specified), then the SpotifyPlus default device is activated. eg: 0d1841b0976bae2a3a310dd74c0f337465899bc8 + [JsonPropertyName("device_id")] + public string? DeviceId { get; init; } + + ///Time delay (in seconds) to wait AFTER issuing the final Connect command (if necessary). This delay will give the spotify web api time to process the device list change before another command is issued. Default is 0.50; value range is 0 - 10. eg: 0.50 + [JsonPropertyName("delay")] + public double? Delay { get; init; } + + ///The relative position in milliseconds to seek to; can be a positive or negative number, or zero if the `positionMS` argument is specified. Example = `-10000` to seek behind by 10 seconds; `10000` to seek ahead by 10 seconds. eg: 10000 + [JsonPropertyName("relative_position_ms")] + public double? RelativePositionMs { get; init; } +} + public partial record SpotifyplusPlayerResolveDeviceIdParameters { ///Entity ID of the SpotifyPlus device that will make the request to the Spotify Web API. eg: media_player.spotifyplus_username @@ -8583,6 +8763,21 @@ public partial record SystemLogWriteParameters public string? Logger { get; init; } } +public partial class TemplateServices +{ + private readonly IHaContext _haContext; + public TemplateServices(IHaContext haContext) + { + _haContext = haContext; + } + + ///Reloads template entities from the YAML-configuration. + public void Reload(object? data = null) + { + _haContext.CallService("template", "reload", null, data); + } +} + public partial class TimerServices { private readonly IHaContext _haContext; 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 new file mode 100644 index 00000000..16a7b34a --- /dev/null +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.cs @@ -0,0 +1,87 @@ +namespace Spotify; + +using FuzzySharp; +using FuzzySharp.Extractor; +using HomeAssistantGenerated; +using NetDaemon.AppModel; +using NetDaemon.HassModel; +using NetDaemon.HassModel.Integration; +using System; +using System.Collections.Generic; +using System.Text.Json; + +record PlayPlaylistData(string? playlist); + +[NetDaemonApp] +public class PlayPlaylist +{ + // Snake-case json options + private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + }; + + public PlayPlaylist(IHaContext ha) + { + ha.RegisterServiceCallBack( + "spotify_play_playlist", + async (e) => + { + string? query = e?.playlist; + + var result = (await ha.CallServiceWithResponseAsync( + "spotifyplus", + "get_playlist_favorites", + data: new SpotifyplusGetPlaylistFavoritesParameters + { + Limit = 50, + EntityId = "media_player.spotifyplus", + SortResult = true, + } + )).Value.Deserialize(_jsonOptions); + + List? myPlaylists = result?.Result?.Items; + + if (query is not null && myPlaylists is not null) + { + PlaylistsItem Query = new(); + Query.Name = query.ToLower(); + + ExtractedResult match = Process.ExtractOne( + Query, + myPlaylists, + new Func((item) => (item.Name ?? "").ToLower()) + ); + + string uri = match.Value!.Uri!; + + if (match.Score < 85) + { + var otherResult = (await ha.CallServiceWithResponseAsync( + "spotifyplus", + "search_playlists", + data: new SpotifyplusSearchPlaylistsParameters + { + Criteria = query, + Limit = 1, + EntityId = "media_player.spotifyplus", + } + )).Value.Deserialize(_jsonOptions); + + uri = otherResult!.Result!.Items![0]!.Uri!; + } + + ha.CallService( + "spotifyplus", + "player_media_play_context", + data: new SpotifyplusPlayerMediaPlayContextParameters + { + ContextUri = uri, + EntityId = "media_player.spotifyplus" + } + ); + } + } + ); + } +} diff --git a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.yaml b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.yaml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/PlayPlaylist.yaml @@ -0,0 +1 @@ + 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 new file mode 100644 index 00000000..fa300b45 --- /dev/null +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/PlayPlaylist/SpotifyplusPlaylistResponse.cs @@ -0,0 +1,38 @@ +namespace Spotify; + +using System.Collections.Generic; + +public class PlaylistsItem +{ + public bool Collaborative { get; set; } + public string? Description { get; set; } + public ExternalUrls? ExternalUrls { get; set; } + public string? Href { get; set; } + public string? Id { get; set; } + public string? ImageUrl { get; set; } + public List? Images { get; set; } + public string? Name { get; set; } + public Owner? Owner { get; set; } + public bool? Public { get; set; } + public string? SnapshotId { get; set; } + public Tracks? Tracks { get; set; } + public string? Type { get; set; } + public string? Uri { get; set; } +} + +public record PlaylistsResult +{ + public string? Href { get; set; } + public int Limit { get; set; } + public object? Next { get; set; } + public int Offset { get; set; } + public object? Previous { get; set; } + public int Total { get; set; } + public List? Items { get; set; } +} + +public class SpotifyplusPlaylistResponse +{ + public UserProfile? UserProfile { get; set; } + public PlaylistsResult? 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 d0a098d9..d3c62673 100644 --- a/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/Types.cs +++ b/devices/homie/modules/home-assistant/netdaemon/apps/Spotify/Types.cs @@ -35,8 +35,25 @@ public record Followers public record Image { public string? Url { get; init; } - public int Height { get; init; } - public int Width { 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 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 class Tracks +{ + public string? Href { get; set; } + public int Total { get; set; } } public record Restrictions { } diff --git a/devices/homie/modules/home-assistant/netdaemon/default.nix b/devices/homie/modules/home-assistant/netdaemon/default.nix index 1a0cdf82..056cf057 100644 --- a/devices/homie/modules/home-assistant/netdaemon/default.nix +++ b/devices/homie/modules/home-assistant/netdaemon/default.nix @@ -68,7 +68,7 @@ in { done done - ${compiled.fetch-deps} . + $(nix build --no-link --print-out-paths --impure --expr "let self = builtins.getFlake (\"$FLAKE\"); inherit (self.nixosConfigurations.homie) pkgs; in (pkgs.callPackage $FLAKE/devices/homie/modules/home-assistant/netdaemon/package.nix {}).fetch-deps") . alejandra . rm -r "$FLAKE/.config" ''; diff --git a/devices/homie/modules/home-assistant/netdaemon/deps.nix b/devices/homie/modules/home-assistant/netdaemon/deps.nix index 05f5107b..0f18038c 100644 --- a/devices/homie/modules/home-assistant/netdaemon/deps.nix +++ b/devices/homie/modules/home-assistant/netdaemon/deps.nix @@ -6,6 +6,11 @@ version = "0.8.4"; hash = "sha256-L9rLcqnQybPoJCcg60h49bjXfqEarM9SFHqOJUMvxz8="; }) + (fetchNuGet { + pname = "FuzzySharp"; + version = "2.0.2"; + hash = "sha256-GuWqVOo+AG8MSvIbusLPjKfJFQRJhSSJ9eGWljTBA/c="; + }) (fetchNuGet { pname = "Microsoft.Extensions.Configuration"; version = "8.0.0"; diff --git a/devices/homie/modules/home-assistant/netdaemon/netdaemon.csproj b/devices/homie/modules/home-assistant/netdaemon/netdaemon.csproj index 87eb8c94..616389a0 100644 --- a/devices/homie/modules/home-assistant/netdaemon/netdaemon.csproj +++ b/devices/homie/modules/home-assistant/netdaemon/netdaemon.csproj @@ -34,5 +34,6 @@ + diff --git a/devices/homie/modules/home-assistant/spotify-sentences.nix b/devices/homie/modules/home-assistant/spotify-sentences.nix index a5208ed5..95520ead 100644 --- a/devices/homie/modules/home-assistant/spotify-sentences.nix +++ b/devices/homie/modules/home-assistant/spotify-sentences.nix @@ -2,15 +2,6 @@ language = "en"; intents = { - PlayArtist.data = [ - { - sentences = [ - "play[ing] [some] music from [the] [artist] {artist}" - "play[ing] [the] artist {artist}" - ]; - } - ]; - PlayAlbum.data = [ { sentences = [ @@ -24,18 +15,32 @@ slots.artist = ""; } ]; + + PlayArtist.data = [ + { + sentences = [ + "play[ing] [some] music from [the] [artist] {artist}" + "play[ing] [the] artist {artist}" + ]; + } + ]; + + PlayPlaylist.data = [ + { + sentences = [ + "play[ing] [the] playlist {playlist}" + ]; + } + ]; }; lists = { - artist.wildcard = true; album.wildcard = true; + artist.wildcard = true; + playlist.wildcard = true; }; responses.intents = { - PlayArtist.default = '' - Searching for {{ slots.artist }} on Spotify and playing their top songs. - ''; - PlayAlbum.default = '' Searching for the album {{ slots.album }} {% if slots.artist != "" %} @@ -43,5 +48,13 @@ {% endif %} on Spotify and playing it. ''; + + PlayArtist.default = '' + Searching for the artist {{ slots.artist }} on Spotify and playing their top songs. + ''; + + PlayPlaylist.default = '' + Searching for {{ slots.playlist }} in your favorites, or elsewhere if not found, and playing it. + ''; }; } diff --git a/devices/homie/modules/home-assistant/spotify.nix b/devices/homie/modules/home-assistant/spotify.nix index a62079bb..d7b4dee4 100644 --- a/devices/homie/modules/home-assistant/spotify.nix +++ b/devices/homie/modules/home-assistant/spotify.nix @@ -35,16 +35,6 @@ in { ]; config.intent_script = { - PlayArtist = { - async_action = "false"; - action = [ - { - service = "netdaemon.spotify_play_artist"; - data.artist = "{{ artist }}"; - } - ]; - }; - PlayAlbum = { async_action = "false"; action = [ @@ -57,6 +47,26 @@ in { } ]; }; + + PlayArtist = { + async_action = "false"; + action = [ + { + service = "netdaemon.spotify_play_artist"; + data.artist = "{{ artist }}"; + } + ]; + }; + + PlayPlaylist = { + async_action = "false"; + action = [ + { + service = "netdaemon.spotify_play_playlist"; + data.playlist = "{{ playlist }}"; + } + ]; + }; }; }; } diff --git a/flake.lock b/flake.lock index 7fcd1254057f225f4e08db888f50a36118e36d49..09c782914faae355f96ea403a277f75d381050cc 100644 GIT binary patch delta 19 bcmexyl|!{R|eF~lH!RU zg*8%Bl9Ej=%~De=j7^P`lTuR5jT6(1jDUQT)I^hHqhw;>-bki!!cCd{ gF~@Loq}8%wX}Gz%#YM?VRtidRHN~6XtzBdY0FgFJbpQYW diff --git a/flake.nix b/flake.nix index aae9c3773b384c7b083a132094709042bb2d0132..41354fb62975994c649db9c2d44e13bc2f0f0c3d 100644 GIT binary patch delta 12 TcmX>WaUy)fYo5)Sym#dQD^dmt delta 66 zcmX>Rek@|cYaY(T_{_YL)Vz}7$%R^a^7(ldW_dYE)?5l;P?TDbuVAa7lmS&W`JtlN O-1P1Q*u; diff --git a/inputs.nix b/inputs.nix index f0e139d6..ef4a3241 100644 --- a/inputs.nix +++ b/inputs.nix @@ -227,10 +227,6 @@ let owner = "fwartner"; repo = "home-assistant-wakewords-collection"; } - { - owner = "don86nl"; - repo = "ha_intents"; - } { owner = "berti24"; repo = "dracul-ha"; diff --git a/outputs.nix b/outputs.nix index 0ad157ed..0c011e72 100644 --- a/outputs.nix +++ b/outputs.nix @@ -182,7 +182,6 @@ nodejs_latest ; }; - text = '' npm i --package-lock-only || true # this command will fail but still updates the main lockfile prefetch-npm-deps ./package-lock.json