Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/youtube.py (Python)

Code before:
        poll_interval = traverse_obj(code_response, 'interval', {int}, default=5)

        for retry in self.RetryManager():
            while True:
                try:
                    token_response = self._download_json(
                        self._OAUTH_TOKEN_ENDPOINT,
                        video_id=self._OAUTH_DISPLAY_ID,
                        note=False,
                        errnote='Failed to request access token',
                        data=json.dumps({
                            'client_id': self._OAUTH_CLIENT_ID,
                            'client_secret': self._OAUTH_CLIENT_SECRET,
                            'device_code': code_response['device_code'],
                            'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
                        }).encode(),
                        headers={'Content-Type': 'application/json'})
                except ExtractorError as e:
                    if isinstance(e.cause, TransportError):
                        retry.error = e
                        break
                    elif isinstance(e.cause, HTTPError):
                        token_response = self._parse_json(
                            self._webpage_read_content(e.cause.response, None, self._OAUTH_DISPLAY_ID, fatal=False) or '{}',
                            video_id=self._OAUTH_DISPLAY_ID, fatal=False)
                        if not (error := traverse_obj(token_response, 'error', {str})):
                            retry.error = e
                            break

                        if error == 'authorization_pending':
                            time.sleep(poll_interval)
                            continue
                        elif error == 'expired_token':
                            raise ExtractorError('Authorization timed out', expected=True, video_id=self._OAUTH_DISPLAY_ID)
                        elif error == 'access_denied':
                            raise ExtractorError('User denied access to account', expected=True, video_id=self._OAUTH_DISPLAY_ID)
                        elif error == 'slow_down':
                            # RFC8628 § 3.5: add 5 seconds to the poll interval
                            poll_interval += 5
                            time.sleep(poll_interval)
                            continue
                        else:
                            raise ExtractorError(
                                f'Authorization server returned an error when fetching access token: {error}',
                                video_id=self._OAUTH_DISPLAY_ID)
                    else:
                        raise

                return token_response

    def _update_oauth(self):

Review comment (security):
I don't like using variables from assignment expressions later / outside of their code path. I'd reuse my suggestion from above (maybe it makes sense as a helper method? `_read_oauth_error` or something)

```suggestion
                        error = traverse_obj(
                            self._webpage_read_content(e.cause.response, self._OAUTH_TOKEN_ENDPOINT, self._OAUTH_DISPLAY_ID, fatal=False),
                            ({json.loads}, 'error', {str}))
                        if not error:
```

Code after:
        self.to_screen(
            f'{self._OAUTH_DISPLAY_ID}: To give yt-dlp access to your account, '
            f'go to  {verification_url}  and enter code  {user_code}')

        # RFC8628 § 3.5: default poll interval is 5 seconds if not provided
        poll_interval = traverse_obj(code_response, ('interval', {int}), default=5)

        for retry in self.RetryManager():
            while True:
                try:
                    token_response = self._download_json(
                        self._OAUTH_TOKEN_ENDPOINT,
                        video_id=self._OAUTH_DISPLAY_ID,
                        note=False,
                        errnote='Failed to request access token',
                        data=json.dumps({
                            'client_id': self._OAUTH_CLIENT_ID,
                            'client_secret': self._OAUTH_CLIENT_SECRET,
                            'device_code': code_response['device_code'],
                            'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
                        }).encode(),
                        headers={'Content-Type': 'application/json'})
                except ExtractorError as e:
                    if isinstance(e.cause, TransportError):
                        retry.error = e
                        break
                    elif isinstance(e.cause, HTTPError):
                        error = self._read_oauth_error_response(e.cause.response)
                        if not error:
                            retry.error = e
                            break

                        if error == 'authorization_pending':
                            time.sleep(poll_interval)
                            continue
                        elif error == 'expired_token':
                            raise ExtractorError(
                                'Authorization timed out', expected=True, video_id=self._OAUTH_DISPLAY_ID)
                        elif error == 'access_denied':
                            raise ExtractorError(
                                'You denied access to an account', expected=True, video_id=self._OAUTH_DISPLAY_ID)
                        elif error == 'slow_down':
                            # RFC8628 § 3.5: add 5 seconds to the poll interval
                            poll_interval += 5
                            time.sleep(poll_interval)
                            continue
                        else:
                            raise ExtractorError(
                                f'Authorization server returned an error when fetching access token: {error}',
                                video_id=self._OAUTH_DISPLAY_ID)
                    raise