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 2 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
46 changes: 36 additions & 10 deletions custom_components/spotcast/spotify/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class SpotifyAccount:
- async_liked_songs
- async_repeat
- async_set_volume
- async_generic_playlists
- async_view

Functions:
- async_from_config_entry
Expand Down Expand Up @@ -917,22 +917,18 @@ async def async_category_playlists(

return playlists

async def async_generic_playlists(
async def async_view(
self,
url: str,
limit: int,
locale: str,
platform: str,
types: str,
) -> list:
"""Fetches playlists based on the provided parameters.
"""Fetches a view based on url.

Args:
- url(str): The url of playlist to fetch (e.g., 'views/made-for-x').
- url(str): The url of the view to fetch (e.g., 'made-for-x').
- limit(int): The maximum number of playlists to retrieve.
- locale(str): The locale for the request (optional).
- platform(str): The platform from which the request is made (default is "web").
- types(str): The types of content to retrieve (default is 'album,playlist,artist,show,station').

Returns:
- list: A list of playlists.
Expand All @@ -941,8 +937,8 @@ async def async_generic_playlists(
params = {
"content_limit": limit,
"locale": locale,
"platform": platform,
"types": types,
"platform": "album,playlist,artist,show,station",
"types": "web",
"limit": limit,
"offset": 0,
}
Expand All @@ -952,6 +948,36 @@ async def async_generic_playlists(
return await self.hass.async_add_executor_job(
partial(self._internal_cont._get, url, None, **params)
)

async def async_search(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THere is already an async_search method. This method is generalised for any type of search. But you would have to build a SearchQuery object to utilize it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

self,
query: str,
searchType: int,
limit: str,
) -> list:
"""Fetches either a playlist or songs based.

Args:
- query(str): The query of the list to fetch (e.g., 'Where is the love').
- searchType(int): what to retrieve playlists or individual songs.
- limit(int): The maximum number of playlists to retrieve.

Returns:
- list: A list of playlists or songs
"""

params = {
"q": query,
"locale": locale,
"type": searchType,
"limit": limit,
}

await self.async_ensure_tokens_valid()

return await self.hass.async_add_executor_job(
partial(self._internal_cont._get, url, None, **params)
)

async def _async_get_count(
self,
Expand Down
13 changes: 9 additions & 4 deletions custom_components/spotcast/websocket/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
player_handler,
cast_devices_handler,
categories_handler,
generic_get_handler,
view_handler,
search_handler
)

WEBSOCKET_ENDPOINTS = MappingProxyType({
Expand Down Expand Up @@ -41,9 +42,13 @@
"handler": categories_handler.async_get_categories,
"schema": categories_handler.SCHEMA,
},
generic_get_handler.ENDPOINT: {
"handler": generic_get_handler.async_get_categories,
"schema": generic_get_handler.SCHEMA,
view_handler.ENDPOINT: {
"handler": view_handler.async_get_categories,
"schema": view_handler.SCHEMA,
},
search_handler.ENDPOINT: {
"handler": search_handler.async_get_categories,
"schema": search_handler.SCHEMA,
},
})

Expand Down
75 changes: 75 additions & 0 deletions custom_components/spotcast/websocket/search_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.core import HomeAssistant
from homeassistant.components.websocket_api import ActiveConnection

from custom_components.spotcast.utils import get_account_entry, search_account
from custom_components.spotcast.spotify.account import SpotifyAccount
from custom_components.spotcast.websocket.utils import websocket_wrapper
from custom_components.spotcast.spotify.utils import select_image_url

ENDPOINT = "spotcast/search"
SCHEMA = vol.Schema(
{
vol.Required("id"): cv.positive_int,
vol.Required("type"): ENDPOINT,
vol.Required("query"): cv.string,
vol.optional("searchType"): cv.string, # Playlist or song, default playlist
vol.Optional("limit"): cv.positive_int,
}
)

@websocket_wrapper
async def search_handler(
hass: HomeAssistant, connection: ActiveConnection, msg: dict
):
"""Searches for a playlist or song.

Args:
- hass (HomeAssistant): The Home Assistant instance.
- connection (ActiveConnection): The active WebSocket connection.
- msg (dict): The message received through the WebSocket API.
"""
account_id = msg.get("account")
query = msg.get("url")
searchType = msg.get("searchType", "playlist")
limit = msg.get("limit", 10)

account: SpotifyAccount

if account_id is None:
entry = get_account_entry(hass)
account_id = entry.entry_id
account = await SpotifyAccount.async_from_config_entry(hass, entry)
else:
account = search_account(hass, account_id)

# prepend view/ to the url
url = f"view/{url}"

result = await account.async_search(
query: query,
searchType: searchType,
limit: limit,
)

formatted_results = [
{
"id": item["id"],
"name": item["name"],
"icon": item["images"][0]["url"]
if "images" in item and len(item["images"]) > 0
else None,
}
for item in result[searchType]
if "id" in item # Only include playlists with an 'id'
]

connection.send_result(
msg["id"],
{
"total": len(formatted_results),
"account": account_id,
"playlists": formatted_results,
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from custom_components.spotcast.websocket.utils import websocket_wrapper
from custom_components.spotcast.spotify.utils import select_image_url

ENDPOINT = "spotcast/get"
ENDPOINT = "spotcast/view"
SCHEMA = vol.Schema(
{
vol.Required("id"): cv.positive_int,
Expand All @@ -22,9 +22,8 @@
}
)


@websocket_wrapper
async def async_get_generic_playlists(
async def view_handler(
hass: HomeAssistant, connection: ActiveConnection, msg: dict
):
"""Gets a list of playlists from a specified account.
Expand All @@ -38,8 +37,6 @@ async def async_get_generic_playlists(
url = msg.get("url")
limit = msg.get("limit", 10)
locale = msg.get("locale", "en_US")
platform = msg.get("platform", "web")
types = msg.get("types", "album,playlist,artist,show,station")

account: SpotifyAccount

Expand All @@ -50,11 +47,13 @@ async def async_get_generic_playlists(
else:
account = search_account(hass, account_id)

playlists = await account.async_generic_playlists(
# prepend view/ to the url
url = f"view/{url}"

playlists = await account.async_view(
url=url,
limit=limit,
locale=locale,
platform=platform,
types=types,
)

Expand All @@ -70,7 +69,6 @@ async def async_get_generic_playlists(
if "id" in playlist # Only include playlists with an 'id'
]

# Send the results back to the WebSocket connection
connection.send_result(
msg["id"],
{
Expand Down