Skip to content

Commit

Permalink
Merge pull request #52 from jareddantis-bots/next
Browse files Browse the repository at this point in the history
  • Loading branch information
jareddantis authored Jan 19, 2024
2 parents a27d636 + eb914d0 commit 470f952
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 30 deletions.
13 changes: 8 additions & 5 deletions cogs/player/jockey.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,11 @@ async def update_now_playing(self):
# Edit message
try:
await np_msg.edit(embed=self.now_playing())
except (HTTPException, Forbidden):
self._logger.warning(
'Failed to edit now playing message for %s',
self.guild.name
)
except (HTTPException, Forbidden) as exc:
# Ignore 404
if not isinstance(exc, NotFound):
self._logger.warning(
'Failed to edit now playing message for %s: %s',
self.guild.name,
exc
)
24 changes: 2 additions & 22 deletions cogs/player/lavalink_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,14 @@
from mafic import Playlist, SearchType, TrackLoadException

from dataclass.lavalink_result import LavalinkResult
from utils.constants import BLACKLIST
from utils.exceptions import LavalinkSearchError
from utils.fuzzy import check_similarity

if TYPE_CHECKING:
from mafic import Node, Track


BLACKLIST = (
'3d'
'8d',
'cover',
'instrumental',
'karaoke',
'live',
'loop',
'mashup',
'minus one',
'performance',
'piano',
'remix',
'rendition',
'reverb',
'slowed',
'sped',
'speed'
)


def filter_results(query: str, search_results: List['Track']) -> List[LavalinkResult]:
"""
Filters search results by removing karaoke, live, instrumental etc versions.
Expand All @@ -52,7 +32,7 @@ def filter_results(query: str, search_results: List['Track']) -> List[LavalinkRe
# if the original query did not ask for it
valid = True
for word in BLACKLIST:
if word in result.title.lower() and not word in query.lower():
if word in result.title.lower() and word not in query.lower():
valid = False
break

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ sentry-sdk==1.39.2
six==1.16.0
sniffio==1.3.0
spotipy==2.23.0
tenacity==8.2.3
thefuzz==0.20.0
typing_extensions==4.9.0
urllib3==2.1.0
Expand Down
22 changes: 22 additions & 0 deletions utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@
path='/v1'
)

# Results with these words in the title will be filtered out
# unless the user's query also contains these words.
BLACKLIST = (
'3d'
'8d',
'cover',
'instrumental',
'karaoke',
'live',
'loop',
'mashup',
'minus one',
'performance',
'piano',
'remix',
'rendition',
'reverb',
'slowed',
'sped',
'speed'
)

# A top search result below this threshold will not be considered for playback
# and Blanco will fall back to YouTube search. See
# jockey_helpers.py:check_similarity_weighted() for the computation.
Expand Down
100 changes: 97 additions & 3 deletions utils/spotify_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,53 @@
from typing import Any, Dict, List, Optional, Tuple

import spotipy
from requests.exceptions import ConnectionError as RequestsConnectionError
from tenacity import (RetryCallState, retry, retry_if_exception_type,
stop_after_attempt, wait_fixed, wait_random)

from dataclass.spotify import SpotifyResult, SpotifyTrack
from database.redis import REDIS
from dataclass.spotify import SpotifyResult, SpotifyTrack

from .constants import BLACKLIST
from .exceptions import SpotifyInvalidURLError, SpotifyNoResultsError
from .logger import create_logger
from .time import human_readable_time

# Retry logger
RETRY_LOGGER = create_logger('spotify_retry')


def log_call(retry_state: RetryCallState) -> None:
"""
Logs an API call
"""
RETRY_LOGGER.debug(
'Calling Spotify API: %s(%s, %s)',
getattr(retry_state.fn, '__name__', repr(retry_state.fn)),
retry_state.args,
retry_state.kwargs
)


def log_failure(retry_state: RetryCallState) -> None:
"""
Logs a retry attempt.
"""
func_name = getattr(retry_state.fn, '__name__', repr(retry_state.fn))

# Log outcome
if retry_state.outcome is not None:
RETRY_LOGGER.debug('%s() failed: %s', func_name, retry_state.outcome)
RETRY_LOGGER.debug(' Exception: %s', retry_state.outcome.exception())
RETRY_LOGGER.debug(' Args: %s', retry_state.args)
RETRY_LOGGER.debug(' Kwargs: %s', retry_state.kwargs)

RETRY_LOGGER.warning(
'Retrying %s(), attempt %s',
func_name,
retry_state.attempt_number
)


def extract_track_info(
track_obj: Dict[str, Any],
Expand Down Expand Up @@ -78,6 +118,13 @@ def __get_art(self, art: List[Dict[str, str]], default='') -> str:
return default
return art[0]['url']

@retry(
retry=retry_if_exception_type(RequestsConnectionError),
stop=stop_after_attempt(3),
wait=wait_fixed(1) + wait_random(0, 2),
before=log_call,
before_sleep=log_failure
)
def get_artist_top_tracks(self, artist_id: str) -> List[SpotifyTrack]:
"""
Returns a list of SpotifyTrack objects for a given artist's
Expand All @@ -89,6 +136,13 @@ def get_artist_top_tracks(self, artist_id: str) -> List[SpotifyTrack]:

return [extract_track_info(track) for track in response['tracks']]

@retry(
retry=retry_if_exception_type(RequestsConnectionError),
stop=stop_after_attempt(3),
wait=wait_fixed(1) + wait_random(0, 2),
before=log_call,
before_sleep=log_failure
)
def get_track_art(self, track_id: str) -> str:
"""
Returns the track artwork for a given track ID.
Expand All @@ -98,6 +152,13 @@ def get_track_art(self, track_id: str) -> str:
raise SpotifyInvalidURLError(f'spotify:track:{track_id}')
return self.__get_art(result['album']['images'])

@retry(
retry=retry_if_exception_type(RequestsConnectionError),
stop=stop_after_attempt(3),
wait=wait_fixed(1) + wait_random(0, 2),
before=log_call,
before_sleep=log_failure
)
def get_track(self, track_id: str) -> SpotifyTrack:
"""
Returns a SpotifyTrack object for a given track ID.
Expand All @@ -118,6 +179,13 @@ def get_track(self, track_id: str) -> SpotifyTrack:

return extract_track_info(result)

@retry(
retry=retry_if_exception_type(RequestsConnectionError),
stop=stop_after_attempt(3),
wait=wait_fixed(1) + wait_random(0, 2),
before=log_call,
before_sleep=log_failure
)
def get_tracks(self, list_type: str, list_id: str) -> Tuple[str, str, List[SpotifyTrack]]:
"""
Returns a list of SpotifyTrack objects for a given album or playlist ID.
Expand Down Expand Up @@ -181,16 +249,42 @@ def get_tracks(self, list_type: str, list_id: str) -> Tuple[str, str, List[Spoti
for x in tracks
]

@retry(
retry=retry_if_exception_type(RequestsConnectionError),
stop=stop_after_attempt(3),
wait=wait_fixed(1) + wait_random(0, 2),
before=log_call,
before_sleep=log_failure
)
def search_track(self, query, limit: int = 1) -> List[SpotifyTrack]:
"""
Searches Spotify for a given query and returns a list of SpotifyTrack objects.
:param query: The name of a track to search for.
:param limit: The maximum number of results to return.
"""
response = self._client.search(query, limit=limit, type='track')
response = self._client.search(query, limit=20, type='track')
if response is None or len(response['tracks']['items']) == 0:
raise SpotifyNoResultsError

return [extract_track_info(track) for track in response['tracks']['items']]
# Filter out tracks with blacklisted words not in the original query
results = []
for result in response['tracks']['items']:
for word in BLACKLIST:
if word in result['name'].lower() and word not in query.lower():
break
else:
results.append(extract_track_info(result))

return results[:limit]

@retry(
retry=retry_if_exception_type(RequestsConnectionError),
stop=stop_after_attempt(3),
wait=wait_fixed(1) + wait_random(0, 2),
before=log_call,
before_sleep=log_failure
)
def search(self, query: str, search_type: str) -> List[SpotifyResult]:
"""
Searches Spotify for a given artist, album, or playlist,
Expand Down

0 comments on commit 470f952

Please sign in to comment.