diff --git a/CHANGELOG.md b/CHANGELOG.md index 127bab0..e117f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ Change are listed in reverse chronological order (newest to oldest). +###### [ 1.0.36 ] - 2024/06/26 + + * Added support for Spotify Connect LoginID specification in configuration options. + * Updated underlying `spotifywebapiPython` package requirement to version 1.0.69. + ###### [ 1.0.35 ] - 2024/06/26 * Updated underlying `spotifywebapiPython` package requirement to version 1.0.68. diff --git a/custom_components/spotifyplus/__init__.py b/custom_components/spotifyplus/__init__.py index c3f32b7..f439d91 100644 --- a/custom_components/spotifyplus/__init__.py +++ b/custom_components/spotifyplus/__init__.py @@ -30,6 +30,7 @@ from .appmessages import STAppMessages from .const import ( + CONF_OPTION_DEVICE_LOGINID, CONF_OPTION_DEVICE_PASSWORD, CONF_OPTION_DEVICE_USERNAME, DOMAIN, @@ -630,6 +631,7 @@ vol.Optional("use_ssl"): cv.boolean, vol.Required("username"): cv.string, vol.Required("password"): cv.string, + vol.Optional("loginid"): cv.string, vol.Optional("pre_disconnect"): cv.boolean, vol.Optional("verify_device_list_entry"): cv.boolean, } @@ -1291,10 +1293,11 @@ async def service_handle_spotify_serviceresponse(service: ServiceCall) -> Servic use_ssl = service.data.get("use_ssl") username = service.data.get("username") password = service.data.get("password") + loginid = service.data.get("loginid") pre_disconnect = service.data.get("pre_disconnect") verify_device_list_entry = service.data.get("verify_device_list_entry") _logsi.LogVerbose(STAppMessages.MSG_SERVICE_EXECUTE % (service.service, entity.name)) - response = await hass.async_add_executor_job(entity.service_spotify_zeroconf_device_connect, username, password, host_ipv4_address, host_ip_port, cpath, version, use_ssl, pre_disconnect, verify_device_list_entry) + response = await hass.async_add_executor_job(entity.service_spotify_zeroconf_device_connect, username, password, loginid, host_ipv4_address, host_ip_port, cpath, version, use_ssl, pre_disconnect, verify_device_list_entry) elif service.service == SERVICE_SPOTIFY_ZEROCONF_DEVICE_DISCONNECT: @@ -2105,7 +2108,8 @@ def _TokenUpdater() -> dict: _TokenUpdater, # tokenUpdater:Callable=None, zeroconf_instance, # zeroconfClient:Zeroconf=None, entry.options.get(CONF_OPTION_DEVICE_USERNAME, None), - entry.options.get(CONF_OPTION_DEVICE_PASSWORD, None) + entry.options.get(CONF_OPTION_DEVICE_PASSWORD, None), + entry.options.get(CONF_OPTION_DEVICE_LOGINID, None), ) _logsi.LogObject(SILevel.Verbose, "'%s': Component async_setup_entry spotifyClient object" % entry.title, spotifyClient) diff --git a/custom_components/spotifyplus/config_flow.py b/custom_components/spotifyplus/config_flow.py index 3fa268e..007cbff 100644 --- a/custom_components/spotifyplus/config_flow.py +++ b/custom_components/spotifyplus/config_flow.py @@ -37,6 +37,7 @@ from .const import ( CONF_OPTION_DEVICE_DEFAULT, + CONF_OPTION_DEVICE_LOGINID, CONF_OPTION_DEVICE_PASSWORD, CONF_OPTION_DEVICE_USERNAME, CONF_OPTION_SCRIPT_TURN_OFF, @@ -365,6 +366,7 @@ async def async_step_init(self, user_input:dict[str,Any]=None) -> FlowResult: # update config entry options from user input values. self._Options[CONF_OPTION_DEVICE_DEFAULT] = user_input.get(CONF_OPTION_DEVICE_DEFAULT, None) + self._Options[CONF_OPTION_DEVICE_LOGINID] = user_input.get(CONF_OPTION_DEVICE_LOGINID, None) self._Options[CONF_OPTION_DEVICE_USERNAME] = user_input.get(CONF_OPTION_DEVICE_USERNAME, None) self._Options[CONF_OPTION_DEVICE_PASSWORD] = user_input.get(CONF_OPTION_DEVICE_PASSWORD, None) self._Options[CONF_OPTION_SCRIPT_TURN_OFF] = user_input.get(CONF_OPTION_SCRIPT_TURN_OFF, None) @@ -372,10 +374,13 @@ async def async_step_init(self, user_input:dict[str,Any]=None) -> FlowResult: # validations. # if device username was entered then device password is required. + deviceLoginid:str = user_input.get(CONF_OPTION_DEVICE_LOGINID, None) deviceUsername:str = user_input.get(CONF_OPTION_DEVICE_USERNAME, None) devicePassword:str = user_input.get(CONF_OPTION_DEVICE_PASSWORD, None) if (deviceUsername is not None) and (devicePassword is None): errors["base"] = "device_password_required" + if (deviceUsername is not None) and (deviceLoginid is None): + errors["base"] = "device_loginid_required" # any validation errors? if not, then ... if "base" not in errors: @@ -395,6 +400,8 @@ async def async_step_init(self, user_input:dict[str,Any]=None) -> FlowResult: # log device that is currently selected. device_default:str = self._Options.get(CONF_OPTION_DEVICE_DEFAULT, None) _logsi.LogVerbose("'%s': OptionsFlow option '%s' - SELECTED value: '%s'" % (self._name, CONF_OPTION_DEVICE_DEFAULT, device_default)) + device_loginid:str = self._Options.get(CONF_OPTION_DEVICE_LOGINID, None) + _logsi.LogVerbose("'%s': OptionsFlow option '%s' - SELECTED value: '%s'" % (self._name, CONF_OPTION_DEVICE_USERNAME, device_loginid)) device_username:str = self._Options.get(CONF_OPTION_DEVICE_USERNAME, None) _logsi.LogVerbose("'%s': OptionsFlow option '%s' - SELECTED value: '%s'" % (self._name, CONF_OPTION_DEVICE_USERNAME, device_username)) @@ -412,6 +419,9 @@ async def async_step_init(self, user_input:dict[str,Any]=None) -> FlowResult: mode=SelectSelectorMode.DROPDOWN ) ), + vol.Optional(CONF_OPTION_DEVICE_LOGINID, + description={"suggested_value": self._Options.get(CONF_OPTION_DEVICE_LOGINID)}, + ): cv.string, vol.Optional(CONF_OPTION_DEVICE_USERNAME, description={"suggested_value": self._Options.get(CONF_OPTION_DEVICE_USERNAME)}, ): cv.string, diff --git a/custom_components/spotifyplus/const.py b/custom_components/spotifyplus/const.py index 6b5971e..34e4eee 100644 --- a/custom_components/spotifyplus/const.py +++ b/custom_components/spotifyplus/const.py @@ -13,6 +13,7 @@ LOGGER = logging.getLogger(__package__) CONF_OPTION_DEVICE_DEFAULT = "device_default" +CONF_OPTION_DEVICE_LOGINID = "device_loginid" CONF_OPTION_DEVICE_PASSWORD = "device_password" CONF_OPTION_DEVICE_USERNAME = "device_username" CONF_OPTION_SCRIPT_TURN_ON = "script_turn_on" diff --git a/custom_components/spotifyplus/instancedata_spotifyplus.py b/custom_components/spotifyplus/instancedata_spotifyplus.py index 150215c..ddf78d4 100644 --- a/custom_components/spotifyplus/instancedata_spotifyplus.py +++ b/custom_components/spotifyplus/instancedata_spotifyplus.py @@ -13,6 +13,7 @@ from .const import ( CONF_OPTION_DEVICE_DEFAULT, + CONF_OPTION_DEVICE_LOGINID, CONF_OPTION_DEVICE_PASSWORD, CONF_OPTION_DEVICE_USERNAME, CONF_OPTION_SCRIPT_TURN_OFF, @@ -63,6 +64,13 @@ def OptionDeviceDefault(self) -> str | None: """ return self.options.get(CONF_OPTION_DEVICE_DEFAULT, None) + @property + def OptionDeviceLoginId(self) -> str | None: + """ + The default Spotify Connect loginid to use when connecting to an inactive device. + """ + return self.options.get(CONF_OPTION_DEVICE_LOGINID, None) + @property def OptionDevicePassword(self) -> str | None: """ diff --git a/custom_components/spotifyplus/manifest.json b/custom_components/spotifyplus/manifest.json index c8f2446..68eba63 100644 --- a/custom_components/spotifyplus/manifest.json +++ b/custom_components/spotifyplus/manifest.json @@ -17,10 +17,10 @@ "requests>=2.31.0", "requests_oauthlib>=1.3.1", "smartinspectPython>=3.0.33", - "spotifywebapiPython>=1.0.68", + "spotifywebapiPython>=1.0.69", "urllib3>=1.21.1,<1.27", "zeroconf>=0.132.2" ], - "version": "1.0.35", + "version": "1.0.36", "zeroconf": [ "_spotify-connect._tcp.local." ] } diff --git a/custom_components/spotifyplus/media_player.py b/custom_components/spotifyplus/media_player.py index 0eee6a7..afc21c5 100644 --- a/custom_components/spotifyplus/media_player.py +++ b/custom_components/spotifyplus/media_player.py @@ -4707,6 +4707,7 @@ def service_spotify_zeroconf_device_connect( self, username:str, password:str, + loginid:str, hostIpv4Address:str, hostIpPort:str, cpath:str, @@ -4721,11 +4722,15 @@ def service_spotify_zeroconf_device_connect( Args: username (str): - Spotify Connect user name to login with. + Spotify Connect user name to login with (e.g. "yourspotifyusername"). This MUST match the account name (or one of them) that was used to configure Spotify Connect - on the manufacturer device. + on the manufacturer device. password (str): Spotify Connect user password to login with. + loginId (str): + Spotify Connect login id to login with (e.g. "31l77fd87g8h9j00k89f07jf87ge"). + This is also known as the canonical user id value. + This MUST be the value that relates to the `username` argument. hostIpv4Address (str): IPV4 address (as a string) at which the Spotify Connect Zeroconf API can be reached on the Spotify Connect device (e.g. "192.168.1.81"). @@ -4781,6 +4786,7 @@ def service_spotify_zeroconf_device_connect( apiMethodParms.AppendKeyValue("useSSL", useSSL) apiMethodParms.AppendKeyValue("username", username) apiMethodParms.AppendKeyValue("password (with mask)", passwordMaskString(password)) + apiMethodParms.AppendKeyValue("loginid", loginid) apiMethodParms.AppendKeyValue("preDisconnect", preDisconnect) apiMethodParms.AppendKeyValue("verifyDeviceListEntry", verifyDeviceListEntry) _logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect ZeroConf Device Connect Service", apiMethodParms) @@ -4817,7 +4823,7 @@ def service_spotify_zeroconf_device_connect( # connect the device to Spotify Connect, which should make it known to any available # Spotify Connect player clients. - result = zconn.Connect(username, password) + result = zconn.Connect(username, password, loginid) # return the (partial) user profile that retrieved the result, as well as the result itself. return { diff --git a/custom_components/spotifyplus/services.yaml b/custom_components/spotifyplus/services.yaml index 34c6f62..d112060 100644 --- a/custom_components/spotifyplus/services.yaml +++ b/custom_components/spotifyplus/services.yaml @@ -2080,18 +2080,25 @@ zeroconf_device_connect: boolean: username: name: Spotify User Name - description: Spotify user name to login with; this can be a standard username (e.g. 'yourusername@mail.com') or a canonical userid (e.g. '31l77y75hfnhk79f7gk6jkk878mg'). This MUST match the account name (or one of them) that was used to configure Spotify Connect on the manufacturer device. + description: Spotify user name to login with (e.g. 'yourusername@mail.com'). This MUST match the account name (or one of them) that was used to configure Spotify Connect on the manufacturer device. example: "yourusername@mail.com" required: true selector: text: password: name: Spotify User Password - description: Spotify user password to login with. + description: Spotify Connect user password to login with. example: "yourpassword" required: true selector: text: + loginid: + name: Spotify Login ID + description: Spotify Connect login id to login with (e.g. "31l77fd87g8h9j00k89f07jf87ge"). This is also known as the canonical user id value. This MUST be the value that relates to the `username` argument. + example: "31l77y75hfnhk79f7gk6jkk878mg" + required: false + selector: + text: pre_disconnect: name: Pre Disconnect? description: True if a Disconnect should be made prior to the Connect call. This will ensure that the active user is logged out, which must be done if switching user accounts; otherwise, False to not issue a Disconnect call. Default is False. diff --git a/custom_components/spotifyplus/strings.json b/custom_components/spotifyplus/strings.json index 170e4f0..ad360f2 100644 --- a/custom_components/spotifyplus/strings.json +++ b/custom_components/spotifyplus/strings.json @@ -24,8 +24,9 @@ "description": "Configure SpotifyPlus integration options that control functionality.", "data": { "device_default": "Default Spotify Connect Player Device ID when none are active.", - "device_password": "Default Spotify Connect password to use when connecting to an inactive device.", - "device_username": "Default Spotify Connect username to use when connecting to an inactive device.", + "device_loginid": "Spotify Connect canonical loginid to use when connecting to an inactive device.", + "device_password": "Spotify Connect password to use when connecting to an inactive device.", + "device_username": "Spotify Connect username to use when connecting to an inactive device.", "script_turn_on": "Script called to turn on device that plays media content.", "script_turn_off": "Script called to turn off device that plays media content." }, @@ -34,7 +35,8 @@ }, "error": { "no_player_devices": "Per Spotify Web API, there are currently no Spotify Connect devices active. Please close the configuration options, play a track on any Spotify Connect player for a minute or two, and then open the configuration options again.", - "device_password_required": "Default Device Password is required if a Default Device Username was specified." + "device_password_required": "Spotify Connect Device Password is required if a Spotify Connect Device Username was specified.", + "device_loginid_required": "Spotify Connect Device LoginId is required if a Spotify Connect Device Username was specified." } }, "system_health": { @@ -1201,11 +1203,15 @@ }, "username": { "name": "Spotify User Name", - "description": "Spotify user name to login with; this can be a standard username (e.g. 'yourusername@mail.com') or a canonical userid (e.g. '31l77y75hfnhk79f7gk6jkk878mg'). This MUST match the account name (or one of them) that was used to configure Spotify Connect on the manufacturer device." + "description": "Spotify user name to login with (e.g. 'yourusername@mail.com'). This MUST match the account name (or one of them) that was used to configure Spotify Connect on the manufacturer device." }, "password": { "name": "Spotify Password", - "description": "Spotify user password to login with." + "description": "Spotify Connect user password to login with." + }, + "loginid": { + "name": "Spotify Login ID", + "description": "Spotify Connect login id to login with (e.g. '31l77fd87g8h9j00k89f07jf87ge'). This is also known as the canonical user id value. This MUST be the value that relates to the `username` argument." }, "pre_disconnect": { "name": "Pre Disconnect?", diff --git a/custom_components/spotifyplus/translations/en.json b/custom_components/spotifyplus/translations/en.json index 170e4f0..ad360f2 100644 --- a/custom_components/spotifyplus/translations/en.json +++ b/custom_components/spotifyplus/translations/en.json @@ -24,8 +24,9 @@ "description": "Configure SpotifyPlus integration options that control functionality.", "data": { "device_default": "Default Spotify Connect Player Device ID when none are active.", - "device_password": "Default Spotify Connect password to use when connecting to an inactive device.", - "device_username": "Default Spotify Connect username to use when connecting to an inactive device.", + "device_loginid": "Spotify Connect canonical loginid to use when connecting to an inactive device.", + "device_password": "Spotify Connect password to use when connecting to an inactive device.", + "device_username": "Spotify Connect username to use when connecting to an inactive device.", "script_turn_on": "Script called to turn on device that plays media content.", "script_turn_off": "Script called to turn off device that plays media content." }, @@ -34,7 +35,8 @@ }, "error": { "no_player_devices": "Per Spotify Web API, there are currently no Spotify Connect devices active. Please close the configuration options, play a track on any Spotify Connect player for a minute or two, and then open the configuration options again.", - "device_password_required": "Default Device Password is required if a Default Device Username was specified." + "device_password_required": "Spotify Connect Device Password is required if a Spotify Connect Device Username was specified.", + "device_loginid_required": "Spotify Connect Device LoginId is required if a Spotify Connect Device Username was specified." } }, "system_health": { @@ -1201,11 +1203,15 @@ }, "username": { "name": "Spotify User Name", - "description": "Spotify user name to login with; this can be a standard username (e.g. 'yourusername@mail.com') or a canonical userid (e.g. '31l77y75hfnhk79f7gk6jkk878mg'). This MUST match the account name (or one of them) that was used to configure Spotify Connect on the manufacturer device." + "description": "Spotify user name to login with (e.g. 'yourusername@mail.com'). This MUST match the account name (or one of them) that was used to configure Spotify Connect on the manufacturer device." }, "password": { "name": "Spotify Password", - "description": "Spotify user password to login with." + "description": "Spotify Connect user password to login with." + }, + "loginid": { + "name": "Spotify Login ID", + "description": "Spotify Connect login id to login with (e.g. '31l77fd87g8h9j00k89f07jf87ge'). This is also known as the canonical user id value. This MUST be the value that relates to the `username` argument." }, "pre_disconnect": { "name": "Pre Disconnect?", diff --git a/requirements.txt b/requirements.txt index f7441eb..3426109 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ colorlog==6.7.0 homeassistant==2024.5.0 ruff==0.1.3 smartinspectPython>=3.0.33 -spotifywebapiPython>=1.0.68 +spotifywebapiPython>=1.0.69