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
Пример #2
0
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")