Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search, view tracks websocket endpoints, service to like media. #489

Merged
merged 45 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fbc89e9
Adds a generic get method
Nov 28, 2024
86beb1e
Adds a generic get method. Which abuses the _get method of spotipy
Nov 28, 2024
6ee3bcc
Add missing import
Nov 28, 2024
c39a817
Generic get => view_handler, adds a search endpoint
Nov 29, 2024
2bc9eb8
Merge branch 'dev' of https://github.com/fondberg/spotcast into gener…
Nov 29, 2024
df4727a
Fix syntax
Nov 29, 2024
891eb4a
Fix url
Nov 29, 2024
5f7decc
Fix mistakes
Nov 29, 2024
3dbd616
Add account in search handler
Nov 29, 2024
de2ff8f
Remove locale in search
Nov 29, 2024
104848f
song => track
Nov 29, 2024
adfe849
Add HREF to search result.
Nov 29, 2024
5cb754c
Use already existing search method. Added comments as explanation
Nov 30, 2024
c91a8ee
Remove unused partial import
Nov 30, 2024
9855836
Align search with new return time.
Nov 30, 2024
c699e59
Merge branch 'dev' of https://github.com/fondberg/spotcast into gener…
Dec 4, 2024
885eeaa
pr comments
Dec 7, 2024
f0fd2d5
Merge branch 'dev' of https://github.com/fondberg/spotcast into gener…
Dec 7, 2024
590eb15
Merge branch 'generic_get_ws' of https://github.com/mikevanes/spotcas…
Dec 7, 2024
1d7199b
Added description to view and search and trackshandler
Dec 10, 2024
3172652
href => uri
Dec 10, 2024
4f2bc8e
Adds a method to like a song.
Dec 10, 2024
62b8d97
Expire liked songs cache after adding liked songs
Dec 10, 2024
e2730ca
Adds websocket to retrieve liked media.
Dec 10, 2024
d55dfc6
Merge branch 'dev' into generic_get_ws
fcusson Dec 10, 2024
a2b2d5d
refactoring unit test
fcusson Dec 11, 2024
b6d66a7
full unit test on websocket handlers
fcusson Dec 11, 2024
55cf26d
added service definition
fcusson Dec 11, 2024
73f8e8f
Merge pull request #1 from fcusson/feature/generic_get_ws
Dec 11, 2024
acac78e
Adds documentation
Dec 12, 2024
39a8be6
typo
fcusson Dec 12, 2024
1b79898
moved config and option flow to config_flow.py for hassfest validation
fcusson Dec 12, 2024
1ff1a9e
tweaked github actions
fcusson Dec 12, 2024
78d4ce7
typo
fcusson Dec 12, 2024
3494ddf
reorg selector strings to fit documentation
fcusson Dec 12, 2024
c82db7f
fixed issue with selector definition
fcusson Dec 12, 2024
9cb29ce
fixed config flow and options flow test with new python module structure
fcusson Dec 12, 2024
d475bb7
added pylint to dev requirements
fcusson Dec 12, 2024
335c33e
fixed option flow for HASS2024.12
fcusson Dec 12, 2024
e8f66e0
bumped minimal version due to breaking change in 2024.12 for option f…
fcusson Dec 12, 2024
f9fd6c3
added pythonpath to linter definition
fcusson Dec 12, 2024
74fa79f
moved location of pythonpath definition
fcusson Dec 12, 2024
097d39d
typo
fcusson Dec 12, 2024
d85a0d3
Fix documentation
Dec 14, 2024
f3f6407
Merge pull request #2 from fcusson/feature/generic_get_ws
Dec 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ async def async_oauth_create_entry(
await private_session.async_ensure_token_valid()
accounts: dict[str, Spotify] = {
"public": Spotify(auth=external_api["token"]["access_token"]),
"private": Spotify(auth=private_session.token)
"private": Spotify(auth=private_session.clean_token)
}

profiles = {}
Expand Down
3 changes: 3 additions & 0 deletions custom_components/spotcast/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
},
"play_saved_episodes": {
"service": "mdi:podcast"
},
"like_media": {
"service": "mdi:music-note-plus"
}
}
}
24 changes: 23 additions & 1 deletion custom_components/spotcast/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,29 @@ add_to_queue:
fields:
spotify_uris:
name: Spotify URIs
description: A list of spotify URIs
description: A list of Spotify URIs
required: true
selector:
object:
uris:
- name: URIs
selector:
text:
multiline: false
account:
name: Spotify Account
description: The id of the spotify account to use. Takes the default account otherwise
required: false
selector:
config_entry:
integration: spotcast
like_media:
name: Like Media
description: Save a list of Media to your Spotify Library
fields:
spotify_uris:
name: Spotify URIs
description: A list of Spotify URIs
required: true
selector:
object:
Expand Down
6 changes: 6 additions & 0 deletions custom_components/spotcast/services/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
PLAY_SAVED_EPISODES,
async_play_saved_episodes,
)
from custom_components.spotcast.services.like_media import (
LIKE_MEDIA_SCHEMA,
async_like_media,
)

SERVICE_SCHEMAS = MappingProxyType({
"play_media": PLAY_MEDIA_SCHEMA,
Expand All @@ -49,6 +53,7 @@
"play_from_search": PLAY_FROM_SEARCH_SCHEMA,
"add_to_queue": ADD_TO_QUEUE_SCHEMA,
"play_saved_episodes": PLAY_SAVED_EPISODES,
"like_media": LIKE_MEDIA_SCHEMA,
})

SERVICE_HANDLERS = MappingProxyType({
Expand All @@ -61,4 +66,5 @@
"play_from_search": async_play_from_search,
"add_to_queue": async_add_to_queue,
"play_saved_episodes": async_play_saved_episodes,
"like_media": async_like_media
})
37 changes: 37 additions & 0 deletions custom_components/spotcast/services/like_media.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Module to like a (list) of spotify uris

Functions:
- async_like_media
"""

from logging import getLogger

from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
import voluptuous as vol

from custom_components.spotcast.spotify import SpotifyAccount
from custom_components.spotcast.utils import get_account_entry
from custom_components.spotcast.spotify.utils import url_to_uri

LOGGER = getLogger(__name__)

LIKE_MEDIA_SCHEMA = vol.Schema({
vol.Required("spotify_uris"): vol.All(cv.ensure_list, [url_to_uri]),
vol.Optional("account"): cv.string,
})


async def async_like_media(hass: HomeAssistant, call: ServiceCall):
"""Service to add like a (list) of spotify uris"""
uris = call.data.get("spotify_uris")
account_id = call.data.get("account")

entry = get_account_entry(hass, account_id)
LOGGER.debug("Loading Spotify Account for User `%s`", account_id)
account = await SpotifyAccount.async_from_config_entry(
hass=hass,
entry=entry
)

await account.async_like_media(uris)
87 changes: 86 additions & 1 deletion custom_components/spotcast/spotify/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
TokenError,
)


LOGGER = getLogger(__name__)


Expand All @@ -51,7 +52,7 @@ class SpotifyAccount:

Properties:
- id(str): the identifier of the account
- name(str): the dusplay name for the account
- name(str): the display name for the account
- profile(dict): the full profile dictionary of the account
- country(str): the country code where the account currently
is.
Expand Down Expand Up @@ -84,8 +85,10 @@ class SpotifyAccount:
- async_apply_extras
- async_shuffle
- async_liked_songs
- async_like_media
- async_repeat
- async_set_volume
- async_view

Functions:
- async_from_config_entry
Expand Down Expand Up @@ -893,6 +896,22 @@ async def async_liked_songs(self, force: bool = False) -> list[str]:

return self.liked_songs

async def async_like_media(self, uris: list[str]):
"""Adds a list of uris to the user's liked songs"""
await self.async_ensure_tokens_valid()

dataset = self._datasets["liked_songs"]

# Force expire the liked_songs dataset
async with dataset.lock:
dataset.expires_at = 0
LOGGER.debug("Expired liked_songs dataset after adding new likes")

await self.hass.async_add_executor_job(
self.apis["private"].current_user_saved_tracks_add,
uris,
)

async def async_repeat(
self,
state: str,
Expand Down Expand Up @@ -1003,6 +1022,72 @@ async def async_category_playlists(

return playlists

async def async_view(
self,
url: str,
language: str = None,
limit: int = None,
) -> list[dict]:
"""Fetches a view based on url.

Args:
- url(str): The url of the view to fetch (e.g.,
'made-for-x').
- language(str, optional): The ISO-639-1 language code to
show the view in. If None, defaults to en_US. Default
is None.
- limit(int, optional): The maximum number of playlists to
retrieve. If None, retrieves all items. Defaults to
None.

Returns:
- list: A list of playlists.
"""

await self.async_ensure_tokens_valid()

locale = None if language is None else f"{language}_{self.country}"

return await self._async_pager(
function=self._fetch_view,
prepends=[url, locale],
limit=25, # This is the max amount per call
max_items=limit,
sub_layer="content",
)

def _fetch_view(
self,
url: str,
locale: str,
limit: int = 25,
offset: int = 0,
) -> dict:
"""Retrieve a page from a view

Args:
- url(str): the endpoint to retrieve
- locale(str): an ISO-639-2 language code
- limit(int, optional): the maximum number of item to
retrieve in each call. Defaults to 25.
- offset(int, optional): the starting index from which to
retrieve elements. Defaults to 0.

Returns:
- dict: an api reply containing the content of a view
"""

params = {
"content_limit": limit,
"locale": locale,
"platform": "web",
"types": "album,playlist,artist,show,station",
"limit": limit,
"offset": offset,
}

return self.apis["private"]._get(url, params)

async def _async_get_count(
self,
function: callable,
Expand Down
Loading
Loading