Initial Home Assistant commit
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
"""main module for spotcast homeassistant utility"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "4.0.1"
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import time
|
||||
|
||||
import homeassistant.core as ha_core
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.const import CONF_ENTITY_ID, CONF_OFFSET, CONF_REPEAT
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import (
|
||||
CONF_ACCOUNTS,
|
||||
CONF_DEVICE_NAME,
|
||||
CONF_FORCE_PLAYBACK,
|
||||
CONF_IGNORE_FULLY_PLAYED,
|
||||
CONF_RANDOM,
|
||||
CONF_SHUFFLE,
|
||||
CONF_SP_DC,
|
||||
CONF_SP_KEY,
|
||||
CONF_SPOTIFY_ACCOUNT,
|
||||
CONF_SPOTIFY_ALBUM_NAME,
|
||||
CONF_SPOTIFY_ARTIST_NAME,
|
||||
CONF_SPOTIFY_AUDIOBOOK_NAME,
|
||||
CONF_SPOTIFY_CATEGORY,
|
||||
CONF_SPOTIFY_COUNTRY,
|
||||
CONF_SPOTIFY_DEVICE_ID,
|
||||
CONF_SPOTIFY_EPISODE_NAME,
|
||||
CONF_SPOTIFY_GENRE_NAME,
|
||||
CONF_SPOTIFY_LIMIT,
|
||||
CONF_SPOTIFY_PLAYLIST_NAME,
|
||||
CONF_SPOTIFY_SHOW_NAME,
|
||||
CONF_SPOTIFY_TRACK_NAME,
|
||||
CONF_SPOTIFY_URI,
|
||||
CONF_START_VOL,
|
||||
DOMAIN,
|
||||
SCHEMA_PLAYLISTS,
|
||||
SCHEMA_WS_ACCOUNTS,
|
||||
SCHEMA_WS_CASTDEVICES,
|
||||
SCHEMA_WS_DEVICES,
|
||||
SCHEMA_WS_PLAYER,
|
||||
CONF_START_POSITION,
|
||||
SERVICE_START_COMMAND_SCHEMA,
|
||||
SPOTCAST_CONFIG_SCHEMA,
|
||||
WS_TYPE_SPOTCAST_ACCOUNTS,
|
||||
WS_TYPE_SPOTCAST_CASTDEVICES,
|
||||
WS_TYPE_SPOTCAST_DEVICES,
|
||||
WS_TYPE_SPOTCAST_PLAYER,
|
||||
WS_TYPE_SPOTCAST_PLAYLISTS,
|
||||
)
|
||||
from .helpers import (
|
||||
add_tracks_to_queue,
|
||||
async_wrap,
|
||||
get_cast_devices,
|
||||
get_random_playlist_from_category,
|
||||
get_search_results,
|
||||
get_spotify_devices,
|
||||
get_spotify_install_status,
|
||||
get_spotify_media_player,
|
||||
is_empty_str,
|
||||
is_valid_uri,
|
||||
url_to_spotify_uri,
|
||||
)
|
||||
from .spotcast_controller import SpotcastController
|
||||
|
||||
CONFIG_SCHEMA = SPOTCAST_CONFIG_SCHEMA
|
||||
DEBUG = True
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass: ha_core.HomeAssistant, config: collections.OrderedDict) -> bool:
|
||||
"""setup method for integration with Home Assistant
|
||||
|
||||
Args:
|
||||
hass (ha_core.HomeAssistant): the HomeAssistant object of the
|
||||
server
|
||||
config (collections.OrderedDict): the configuration of the
|
||||
server
|
||||
|
||||
Returns:
|
||||
bool: returns a bollean based on if the setup wroked or not
|
||||
"""
|
||||
|
||||
# get spotify core integration status
|
||||
# if return false, could indicate a bad spotify integration. Race
|
||||
# condition doesn't permit us to abort setup, see #258
|
||||
if not get_spotify_install_status(hass):
|
||||
_LOGGER.debug(
|
||||
"Spotify integration was not found, please verify integration is "
|
||||
"functionnal. Could result in python error..."
|
||||
)
|
||||
|
||||
# Setup the Spotcast service.
|
||||
conf = config[DOMAIN]
|
||||
|
||||
sp_dc = conf[CONF_SP_DC]
|
||||
sp_key = conf[CONF_SP_KEY]
|
||||
accounts = conf.get(CONF_ACCOUNTS)
|
||||
|
||||
spotcast_controller = SpotcastController(hass, sp_dc, sp_key, accounts)
|
||||
|
||||
if DOMAIN not in hass.data:
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN]["controller"] = spotcast_controller
|
||||
|
||||
@callback
|
||||
def websocket_handle_playlists(
|
||||
hass: ha_core.HomeAssistant,
|
||||
connection,
|
||||
msg: str
|
||||
):
|
||||
@async_wrap
|
||||
def get_playlist():
|
||||
"""Handle to get playlist"""
|
||||
playlist_type = msg.get("playlist_type")
|
||||
country_code = msg.get("country_code")
|
||||
locale = msg.get("locale", "en")
|
||||
limit = msg.get("limit", 10)
|
||||
account = msg.get("account", None)
|
||||
|
||||
_LOGGER.debug("websocket_handle_playlists msg: %s", msg)
|
||||
resp = spotcast_controller.get_playlists(
|
||||
account, playlist_type, country_code, locale, limit
|
||||
)
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg["id"], resp))
|
||||
|
||||
hass.async_add_job(get_playlist())
|
||||
|
||||
@callback
|
||||
def websocket_handle_devices(
|
||||
hass: ha_core.HomeAssistant,
|
||||
connection,
|
||||
msg: str,
|
||||
):
|
||||
@async_wrap
|
||||
def get_devices():
|
||||
"""Handle to get devices. Only for default account"""
|
||||
account = msg.get("account", None)
|
||||
client = spotcast_controller.get_spotify_client(account)
|
||||
me_resp = client._get("me") # pylint: disable=W0212
|
||||
spotify_media_player = get_spotify_media_player(
|
||||
hass, me_resp["id"])
|
||||
resp = get_spotify_devices(spotify_media_player, hass)
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg["id"], resp))
|
||||
|
||||
hass.async_add_job(get_devices())
|
||||
|
||||
@callback
|
||||
def websocket_handle_player(
|
||||
hass: ha_core.HomeAssistant,
|
||||
connection,
|
||||
msg: str,
|
||||
):
|
||||
@async_wrap
|
||||
def get_player():
|
||||
"""Handle to get player"""
|
||||
account = msg.get("account", None)
|
||||
_LOGGER.debug("websocket_handle_player msg: %s", msg)
|
||||
client = spotcast_controller.get_spotify_client(account)
|
||||
resp = client._get("me/player") # pylint: disable=W0212
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg["id"], resp))
|
||||
|
||||
hass.async_add_job(get_player())
|
||||
|
||||
@callback
|
||||
def websocket_handle_accounts(
|
||||
hass: ha_core.HomeAssistant,
|
||||
connection,
|
||||
msg: str,
|
||||
):
|
||||
"""Handle to get accounts"""
|
||||
_LOGGER.debug("websocket_handle_accounts msg: %s", msg)
|
||||
resp = list(accounts.keys()) if accounts is not None else []
|
||||
resp.append("default")
|
||||
connection.send_message(websocket_api.result_message(msg["id"], resp))
|
||||
|
||||
@callback
|
||||
def websocket_handle_castdevices(
|
||||
hass: ha_core.HomeAssistant,
|
||||
connection, msg: str
|
||||
):
|
||||
"""Handle to get cast devices for debug purposes"""
|
||||
_LOGGER.debug("websocket_handle_castdevices msg: %s", msg)
|
||||
|
||||
known_devices = get_cast_devices(hass)
|
||||
_LOGGER.debug("%s", known_devices)
|
||||
resp = [
|
||||
{
|
||||
"uuid": str(cast_info.cast_info.uuid),
|
||||
"model_name": cast_info.cast_info.model_name,
|
||||
"friendly_name": cast_info.cast_info.friendly_name,
|
||||
}
|
||||
for cast_info in known_devices
|
||||
]
|
||||
|
||||
connection.send_message(websocket_api.result_message(msg["id"], resp))
|
||||
|
||||
def start_casting(call: ha_core.ServiceCall):
|
||||
"""service called."""
|
||||
uri = call.data.get(CONF_SPOTIFY_URI)
|
||||
category = call.data.get(CONF_SPOTIFY_CATEGORY)
|
||||
country = call.data.get(CONF_SPOTIFY_COUNTRY)
|
||||
limit = call.data.get(CONF_SPOTIFY_LIMIT)
|
||||
artistName = call.data.get(CONF_SPOTIFY_ARTIST_NAME)
|
||||
albumName = call.data.get(CONF_SPOTIFY_ALBUM_NAME)
|
||||
playlistName = call.data.get(CONF_SPOTIFY_PLAYLIST_NAME)
|
||||
trackName = call.data.get(CONF_SPOTIFY_TRACK_NAME)
|
||||
showName = call.data.get(CONF_SPOTIFY_SHOW_NAME)
|
||||
episodeName = call.data.get(CONF_SPOTIFY_EPISODE_NAME)
|
||||
audiobookName = call.data.get(CONF_SPOTIFY_AUDIOBOOK_NAME)
|
||||
genreName = call.data.get(CONF_SPOTIFY_GENRE_NAME)
|
||||
random_song = call.data.get(CONF_RANDOM, False)
|
||||
repeat = call.data.get(CONF_REPEAT, False)
|
||||
shuffle = call.data.get(CONF_SHUFFLE, False)
|
||||
start_volume = call.data.get(CONF_START_VOL)
|
||||
spotify_device_id = call.data.get(CONF_SPOTIFY_DEVICE_ID)
|
||||
position = call.data.get(CONF_OFFSET)
|
||||
start_position = call.data.get(CONF_START_POSITION)
|
||||
force_playback = call.data.get(CONF_FORCE_PLAYBACK)
|
||||
account = call.data.get(CONF_SPOTIFY_ACCOUNT)
|
||||
ignore_fully_played = call.data.get(CONF_IGNORE_FULLY_PLAYED)
|
||||
device_name = call.data.get(CONF_DEVICE_NAME)
|
||||
entity_id = call.data.get(CONF_ENTITY_ID)
|
||||
|
||||
try: # yes this is ugly, quick fix while working on V4
|
||||
|
||||
# if no market information try to get global setting
|
||||
if is_empty_str(country):
|
||||
try:
|
||||
country = config[DOMAIN][CONF_SPOTIFY_COUNTRY]
|
||||
except KeyError:
|
||||
country = None
|
||||
|
||||
client = spotcast_controller.get_spotify_client(account)
|
||||
|
||||
# verify the uri provided and clean-up if required
|
||||
if not is_empty_str(uri):
|
||||
|
||||
# remove ? from badly formatted URI
|
||||
uri = uri.split("?")[0]
|
||||
|
||||
if uri.startswith("http"):
|
||||
try:
|
||||
u = url_to_spotify_uri(uri)
|
||||
_LOGGER.debug(
|
||||
"converted web URL %s to spotify URI %s", uri, u)
|
||||
uri = u
|
||||
except ValueError:
|
||||
_LOGGER.error(
|
||||
"invalid web URL provided, could not convert to spotify URI: %s", uri)
|
||||
|
||||
if not is_valid_uri(uri):
|
||||
_LOGGER.error("Invalid URI provided, aborting casting")
|
||||
return
|
||||
|
||||
# force first two elements of uri to lowercase
|
||||
uri = uri.split(":")
|
||||
uri[0] = uri[0].lower()
|
||||
uri[1] = uri[1].lower()
|
||||
uri = ":".join(uri)
|
||||
|
||||
# first, rely on spotify id given in config otherwise get one
|
||||
if not spotify_device_id:
|
||||
spotify_device_id = spotcast_controller.get_spotify_device_id(
|
||||
account, spotify_device_id, device_name, entity_id
|
||||
)
|
||||
|
||||
if start_position is not None:
|
||||
start_position *= 1000
|
||||
|
||||
if (
|
||||
is_empty_str(uri)
|
||||
and len(
|
||||
list(
|
||||
filter(
|
||||
lambda x: not is_empty_str(x),
|
||||
[
|
||||
artistName,
|
||||
playlistName,
|
||||
trackName,
|
||||
showName,
|
||||
episodeName,
|
||||
audiobookName,
|
||||
genreName,
|
||||
category,
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
== 0
|
||||
):
|
||||
_LOGGER.debug("Transfering playback")
|
||||
current_playback = client.current_playback()
|
||||
if current_playback is not None:
|
||||
_LOGGER.debug("Current_playback from spotify: %s",
|
||||
current_playback)
|
||||
force_playback = True
|
||||
_LOGGER.debug("Force playback: %s", force_playback)
|
||||
client.transfer_playback(
|
||||
device_id=spotify_device_id, force_play=force_playback
|
||||
)
|
||||
elif not is_empty_str(category):
|
||||
uri = get_random_playlist_from_category(
|
||||
client, category, country, limit)
|
||||
|
||||
if uri is None:
|
||||
_LOGGER.error("No playlist returned. Stop service call")
|
||||
return None
|
||||
|
||||
spotcast_controller.play(
|
||||
client,
|
||||
spotify_device_id,
|
||||
uri,
|
||||
random_song,
|
||||
position,
|
||||
ignore_fully_played,
|
||||
start_position,
|
||||
)
|
||||
else:
|
||||
searchResults = []
|
||||
if is_empty_str(uri):
|
||||
# get uri from search request
|
||||
searchResults = get_search_results(
|
||||
spotify_client=client,
|
||||
limit=limit,
|
||||
artistName=artistName,
|
||||
country=country,
|
||||
albumName=albumName,
|
||||
playlistName=playlistName,
|
||||
trackName=trackName,
|
||||
showName=showName,
|
||||
episodeName=episodeName,
|
||||
audiobookName=audiobookName,
|
||||
genreName=genreName,
|
||||
)
|
||||
# play the first track
|
||||
if len(searchResults) > 0:
|
||||
uri = searchResults[0]["uri"]
|
||||
|
||||
spotcast_controller.play(
|
||||
client,
|
||||
spotify_device_id,
|
||||
uri,
|
||||
random_song,
|
||||
position,
|
||||
ignore_fully_played,
|
||||
start_position,
|
||||
)
|
||||
|
||||
if len(searchResults) > 1:
|
||||
add_tracks_to_queue(client, searchResults[1:])
|
||||
|
||||
if start_volume <= 100:
|
||||
_LOGGER.debug("Setting volume to %d", start_volume)
|
||||
time.sleep(2)
|
||||
client.volume(volume_percent=start_volume,
|
||||
device_id=spotify_device_id)
|
||||
if shuffle:
|
||||
_LOGGER.debug("Turning shuffle on")
|
||||
time.sleep(3)
|
||||
client.shuffle(state=shuffle, device_id=spotify_device_id)
|
||||
if repeat:
|
||||
_LOGGER.debug("Turning repeat on")
|
||||
time.sleep(3)
|
||||
client.repeat(state=repeat, device_id=spotify_device_id)
|
||||
|
||||
except Exception as exc:
|
||||
if DEBUG:
|
||||
raise exc
|
||||
|
||||
raise HomeAssistantError(exc) from exc
|
||||
|
||||
# Register websocket and service
|
||||
websocket_api.async_register_command(
|
||||
hass=hass,
|
||||
command_or_handler=WS_TYPE_SPOTCAST_PLAYLISTS,
|
||||
handler=websocket_handle_playlists,
|
||||
schema=SCHEMA_PLAYLISTS,
|
||||
)
|
||||
websocket_api.async_register_command(
|
||||
hass=hass,
|
||||
command_or_handler=WS_TYPE_SPOTCAST_DEVICES,
|
||||
handler=websocket_handle_devices,
|
||||
schema=SCHEMA_WS_DEVICES,
|
||||
)
|
||||
websocket_api.async_register_command(
|
||||
hass=hass,
|
||||
command_or_handler=WS_TYPE_SPOTCAST_PLAYER,
|
||||
handler=websocket_handle_player,
|
||||
schema=SCHEMA_WS_PLAYER,
|
||||
)
|
||||
|
||||
websocket_api.async_register_command(
|
||||
hass=hass,
|
||||
command_or_handler=WS_TYPE_SPOTCAST_ACCOUNTS,
|
||||
handler=websocket_handle_accounts,
|
||||
schema=SCHEMA_WS_ACCOUNTS,
|
||||
)
|
||||
|
||||
websocket_api.async_register_command(
|
||||
hass=hass,
|
||||
command_or_handler=WS_TYPE_SPOTCAST_CASTDEVICES,
|
||||
handler=websocket_handle_castdevices,
|
||||
schema=SCHEMA_WS_CASTDEVICES,
|
||||
)
|
||||
|
||||
hass.services.register(
|
||||
domain=DOMAIN,
|
||||
service="start",
|
||||
service_func=start_casting,
|
||||
schema=SERVICE_START_COMMAND_SCHEMA,
|
||||
)
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user