def test_user_oauth_token_has_expired(self): service = SpotifyService() # has expired user = { 'token_expires': datetime.datetime(2021, 5, 12, 3, 0, 40, tzinfo=datetime.timezone.utc) } assert service.user_oauth_token_has_expired(user) is True # expires within the 5 minute threshold user = { 'token_expires': datetime.datetime(2021, 5, 12, 3, 24, 40, tzinfo=datetime.timezone.utc) } assert service.user_oauth_token_has_expired(user) is True # hasn't expired user = { 'token_expires': datetime.datetime(2021, 5, 12, 4, 1, 40, tzinfo=datetime.timezone.utc) } assert service.user_oauth_token_has_expired(user) is False
def process_one_user(user: dict, service: SpotifyService) -> int: """ Get recently played songs for this user and submit them to ListenBrainz. Args: user (spotify.Spotify): the user whose plays are to be imported. service (listenbrainz.domain.spotify.SpotifyService): service to process users Raises: spotify.SpotifyAPIError: if we encounter errors from the Spotify API. spotify.SpotifyListenBrainzError: if we encounter a rate limit, even after retrying. or if we get errors while submitting the data to ListenBrainz Returns: the number of recently played listens imported for the user """ try: if service.user_oauth_token_has_expired(user): user = service.refresh_access_token(user['user_id'], user['refresh_token']) listens = [] latest_listened_at = None # If there is no playback, currently_playing will be None. # There are two playing types, track and episode. We use only the # track type. Therefore, when the user's playback type is not a track, # Spotify will set the item field to null which becomes None after # parsing the JSON. Due to these reasons, we cannot simplify the # checks below. currently_playing = get_user_currently_playing(user) if currently_playing is not None: currently_playing_item = currently_playing.get('item', None) if currently_playing_item is not None: current_app.logger.debug( 'Received a currently playing track for %s', str(user)) listens, latest_listened_at = parse_and_validate_spotify_plays( [currently_playing_item], LISTEN_TYPE_PLAYING_NOW) if listens: submit_listens_to_listenbrainz( user, listens, listen_type=LISTEN_TYPE_PLAYING_NOW) recently_played = get_user_recently_played(user) if recently_played is not None and 'items' in recently_played: listens, latest_listened_at = parse_and_validate_spotify_plays( recently_played['items'], LISTEN_TYPE_IMPORT) current_app.logger.debug('Received %d tracks for %s', len(listens), str(user)) # if we don't have any new listens, return. we don't check whether the listens list is empty here # because it will empty in both cases where we don't receive any listens and when we receive only # bad listens. so instead we check latest_listened_at which is None only in case when we received # nothing from spotify. if latest_listened_at is None: service.update_user_import_status(user['user_id']) return 0 submit_listens_to_listenbrainz(user, listens, listen_type=LISTEN_TYPE_IMPORT) # we've succeeded so update the last_updated and latest_listened_at field for this user service.update_latest_listen_ts(user['user_id'], latest_listened_at) current_app.logger.info('imported %d listens for %s' % (len(listens), str(user['musicbrainz_id']))) return len(listens) except ExternalServiceInvalidGrantError: error_message = "It seems like you've revoked permission for us to read your spotify account" service.update_user_import_status(user_id=user['user_id'], error=error_message) if not current_app.config['TESTING']: notify_error(user['musicbrainz_id'], error_message) # user has revoked authorization through spotify ui or deleted their spotify account etc. # in any of these cases, we should delete the user's token from. service.revoke_user(user['user_id']) raise ExternalServiceError("User has revoked spotify authorization") except ExternalServiceAPIError as e: # if it is an error from the Spotify API, show the error message to the user service.update_user_import_status(user_id=user['user_id'], error=str(e)) if not current_app.config['TESTING']: notify_error(user['musicbrainz_id'], str(e)) raise ExternalServiceError("Could not refresh user token from spotify")