Return success message if playback is altered (#145)

# PR Description
Previously, if a tool adjusted the playback state, then the tool would
return the current playback state after the modification had occurred.
The problem with this approach was that Spotify would not update the
playback state in time (sometimes), so the tools were returning stale
data!
This commit is contained in:
Eric Gustin 2024-11-04 17:22:10 -08:00 committed by GitHub
parent 39d342bd67
commit bc393db305
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 32 additions and 59 deletions

View file

@ -8,7 +8,6 @@ from arcade_spotify.tools.search import search
from arcade_spotify.tools.utils import ( from arcade_spotify.tools.utils import (
convert_to_playback_state, convert_to_playback_state,
get_url, get_url,
handle_404_playback_state,
send_spotify_request, send_spotify_request,
) )
@ -24,7 +23,7 @@ async def adjust_playback_position(
Optional[int], Optional[int],
"The relative position from the current playback position in milliseconds to seek to", "The relative position from the current playback position in milliseconds to seek to",
] = None, ] = None,
) -> Annotated[dict, "The updated playback state"]: ) -> Annotated[str, "Success/failure message"]:
"""Adjust the playback position within the currently playing track. """Adjust the playback position within the currently playing track.
Knowledge of the current playback state is NOT needed to use this tool as it handles Knowledge of the current playback state is NOT needed to use this tool as it handles
@ -47,8 +46,7 @@ async def adjust_playback_position(
if relative_position_ms is not None: if relative_position_ms is not None:
playback_state = await get_playback_state(context) playback_state = await get_playback_state(context)
if playback_state.get("device_id") is None: if playback_state.get("device_id") is None:
playback_state["message"] = "No track to adjust position" return "No track to adjust position"
return playback_state
absolute_position_ms = playback_state["progress_ms"] + relative_position_ms absolute_position_ms = playback_state["progress_ms"] + relative_position_ms
@ -59,82 +57,71 @@ async def adjust_playback_position(
response = await send_spotify_request(context, "PUT", url, params=params) response = await send_spotify_request(context, "PUT", url, params=params)
playback_state = handle_404_playback_state(response, "No track to adjust position", False) if response.status_code == 404:
if playback_state: return "No track to adjust position"
return playback_state
response.raise_for_status() response.raise_for_status()
playback_state = await get_playback_state(context) return "Playback position adjusted"
return playback_state
# NOTE: This tool only works for Spotify Premium users # NOTE: This tool only works for Spotify Premium users
@tool(requires_auth=Spotify(scopes=["user-read-playback-state", "user-modify-playback-state"])) @tool(requires_auth=Spotify(scopes=["user-read-playback-state", "user-modify-playback-state"]))
async def skip_to_previous_track( async def skip_to_previous_track(
context: ToolContext, context: ToolContext,
) -> Annotated[dict, "The updated playback state"]: ) -> Annotated[str, "Success/failure message"]:
"""Skip to the previous track in the user's queue, if any""" """Skip to the previous track in the user's queue, if any"""
url = get_url("player_skip_to_previous") url = get_url("player_skip_to_previous")
response = await send_spotify_request(context, "POST", url) response = await send_spotify_request(context, "POST", url)
playback_state = handle_404_playback_state(response, "No track to go back to", False) if response.status_code == 404:
if playback_state: return "No track to go back to"
return playback_state
response.raise_for_status() response.raise_for_status()
playback_state = await get_playback_state(context) return "Playback skipped to previous track"
return playback_state
# NOTE: This tool only works for Spotify Premium users # NOTE: This tool only works for Spotify Premium users
@tool(requires_auth=Spotify(scopes=["user-read-playback-state", "user-modify-playback-state"])) @tool(requires_auth=Spotify(scopes=["user-read-playback-state", "user-modify-playback-state"]))
async def skip_to_next_track( async def skip_to_next_track(
context: ToolContext, context: ToolContext,
) -> Annotated[dict, "The updated playback state"]: ) -> Annotated[str, "Success/failure message"]:
"""Skip to the next track in the user's queue, if any""" """Skip to the next track in the user's queue, if any"""
url = get_url("player_skip_to_next") url = get_url("player_skip_to_next")
response = await send_spotify_request(context, "POST", url) response = await send_spotify_request(context, "POST", url)
playback_state = handle_404_playback_state(response, "No track to skip", False) if response.status_code == 404:
if playback_state: return "No track to skip"
return playback_state
response.raise_for_status() response.raise_for_status()
playback_state = await get_playback_state(context) return "Playback skipped to next track"
return playback_state
# NOTE: This tool only works for Spotify Premium users # NOTE: This tool only works for Spotify Premium users
@tool(requires_auth=Spotify(scopes=["user-read-playback-state", "user-modify-playback-state"])) @tool(requires_auth=Spotify(scopes=["user-read-playback-state", "user-modify-playback-state"]))
async def pause_playback( async def pause_playback(
context: ToolContext, context: ToolContext,
) -> Annotated[dict, "The updated playback state"]: ) -> Annotated[str, "Success/failure message"]:
"""Pause the currently playing track, if any""" """Pause the currently playing track, if any"""
playback_state = await get_playback_state(context) playback_state = await get_playback_state(context)
# There is no current state, therefore nothing to pause # There is no current state, therefore nothing to pause
if playback_state.get("device_id") is None: if playback_state.get("device_id") is None:
playback_state["message"] = "No track to pause" return "No track to pause"
return playback_state
# Track is already paused # Track is already paused
if playback_state.get("is_playing") is False: if playback_state.get("is_playing") is False:
playback_state["message"] = "Track is already paused" return "Track is already paused"
return playback_state
url = get_url("player_pause_playback") url = get_url("player_pause_playback")
response = await send_spotify_request(context, "PUT", url) response = await send_spotify_request(context, "PUT", url)
response.raise_for_status() response.raise_for_status()
playback_state["is_playing"] = False return "Playback paused"
return playback_state
# NOTE: This tool only works for Spotify Premium users # NOTE: This tool only works for Spotify Premium users
@ -145,26 +132,23 @@ async def pause_playback(
) )
async def resume_playback( async def resume_playback(
context: ToolContext, context: ToolContext,
) -> Annotated[dict, "The updated playback state"]: ) -> Annotated[str, "Success/failure message"]:
"""Resume the currently playing track, if any""" """Resume the currently playing track, if any"""
playback_state = await get_playback_state(context) playback_state = await get_playback_state(context)
# There is no current state, therefore nothing to resume # There is no current state, therefore nothing to resume
if playback_state.get("device_id") is None: if playback_state.get("device_id") is None:
playback_state["message"] = "No track to resume" return "No track to resume"
return playback_state
# Track is already playing # Track is already playing
if playback_state.get("is_playing") is True: if playback_state.get("is_playing") is True:
playback_state["message"] = "Track is already playing" return "Track is already playing"
return playback_state
url = get_url("player_modify_playback") url = get_url("player_modify_playback")
response = await send_spotify_request(context, "PUT", url) response = await send_spotify_request(context, "PUT", url)
response.raise_for_status() response.raise_for_status()
playback_state["is_playing"] = True return "Playback resumed"
return playback_state
# NOTE: This tool only works for Spotify Premium users # NOTE: This tool only works for Spotify Premium users
@ -183,7 +167,7 @@ async def start_tracks_playback_by_id(
Optional[int], Optional[int],
"The position in milliseconds to start the first track from", "The position in milliseconds to start the first track from",
] = 0, ] = 0,
) -> Annotated[dict, "The updated playback state"]: ) -> Annotated[str, "Success/failure message"]:
"""Start playback of a list of tracks (songs)""" """Start playback of a list of tracks (songs)"""
devices = [ devices = [
@ -204,16 +188,13 @@ async def start_tracks_playback_by_id(
} }
response = await send_spotify_request(context, "PUT", url, params=params, json_data=body) response = await send_spotify_request(context, "PUT", url, params=params, json_data=body)
playback_state = handle_404_playback_state(
response, "Cannot start playback because no active device is available", False if response.status_code == 404:
) return "Cannot start playback because no active device is available"
if playback_state:
return playback_state
response.raise_for_status() response.raise_for_status()
playback_state = await get_playback_state(context) return "Playback started"
return playback_state
@tool(requires_auth=Spotify(scopes=["user-read-playback-state"])) @tool(requires_auth=Spotify(scopes=["user-read-playback-state"]))
@ -252,7 +233,7 @@ async def get_currently_playing(
async def play_artist_by_name( async def play_artist_by_name(
context: ToolContext, context: ToolContext,
name: Annotated[str, "The name of the artist to play"], name: Annotated[str, "The name of the artist to play"],
) -> Annotated[dict, "The updated playback state"]: ) -> Annotated[str, "Success/failure message"]:
"""Plays a song by an artist and queues four more songs by the same artist""" """Plays a song by an artist and queues four more songs by the same artist"""
q = f"artist:{name}" q = f"artist:{name}"
search_results = await search(context, q, [SearchType.TRACK], 5) search_results = await search(context, q, [SearchType.TRACK], 5)
@ -264,9 +245,9 @@ async def play_artist_by_name(
retry_after_ms=500, retry_after_ms=500,
) )
track_ids = [item["id"] for item in search_results["tracks"]["items"]] track_ids = [item["id"] for item in search_results["tracks"]["items"]]
playback_state = await start_tracks_playback_by_id(context, track_ids) response = await start_tracks_playback_by_id(context, track_ids)
return playback_state return response
# NOTE: This tool only works for Spotify Premium users # NOTE: This tool only works for Spotify Premium users
@ -279,7 +260,7 @@ async def play_track_by_name(
context: ToolContext, context: ToolContext,
track_name: Annotated[str, "The name of the track to play"], track_name: Annotated[str, "The name of the track to play"],
artist_name: Annotated[Optional[str], "The name of the artist of the track"] = None, artist_name: Annotated[Optional[str], "The name of the artist of the track"] = None,
) -> Annotated[dict, "The updated playback state"]: ) -> Annotated[str, "Success/failure message"]:
"""Plays a song by name""" """Plays a song by name"""
q = f"track:{track_name}" q = f"track:{track_name}"
if artist_name: if artist_name:
@ -297,9 +278,9 @@ async def play_track_by_name(
) )
track_id = search_results["tracks"]["items"][0]["id"] track_id = search_results["tracks"]["items"][0]["id"]
playback_state = await start_tracks_playback_by_id(context, [track_id]) response = await start_tracks_playback_by_id(context, [track_id])
return playback_state return response
# NOTE: This tool only works for Spotify Premium users # NOTE: This tool only works for Spotify Premium users

View file

@ -36,14 +36,6 @@ async def send_spotify_request(
return response return response
def handle_404_playback_state(response, message, is_playing: bool) -> dict | None:
if response.status_code == 404:
return convert_to_playback_state({
"is_playing": is_playing,
"message": message,
}).to_dict()
def get_url(endpoint: str, **kwargs) -> str: def get_url(endpoint: str, **kwargs) -> str:
""" """
Get the full Spotify URL for a given endpoint. Get the full Spotify URL for a given endpoint.