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

Code before:
    traverse_obj,
    url_or_none,
)


class AGalegaBaseIE(InfoExtractor):
    _VALID_URL = False

    def _get_access_token(self, video_id: str) -> dict[str, str] | None:
        token_url = 'https://www.agalega.gal/api/fetch-api/jwt/token'
        body_data = json.dumps({
            'username': None,
            'password': None,
            'client': 'crtvg',
            'checkExistsCookies': False,
        }).encode()

        token_raw = self._download_json(token_url, video_id, data=body_data)

        if token_raw is not None:
            access_token = str_or_none(token_raw.get('access'))
            if access_token is not None:
                return {'authorization': f'jwtok {access_token}'}
            else:
                return None
        return None


class AGalegaVideosIE(AGalegaBaseIE):
    IE_NAME = 'agalega:videos'
    _VALID_URL = r'https?://(?:www\.)?agalega\.gal/videos/(?:detail/)?(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://www.agalega.gal/videos/288664-lr-ninguencheconta',
        'md5': '04533a66c5f863d08dd9724b11d1c223',
        'info_dict': {
            'id': '288664',
            'title': 'Roberto e Ángel Martín atenden consultas dos espectadores',
            'description': 'O cómico ademais fai un repaso dalgúns momentos da súa traxectoria profesional',
            'thumbnail': 'https://crtvg-bucket.flumotion.cloud/content_cards/2ef32c3b9f6249d9868fd8f11d389d3d.png',
            'ext': 'mp4',
        },
    },
        {
        'url': 'https://www.agalega.gal/videos/detail/296152-pulso-activo-7',
        'md5': '26df7fdcf859f38ad92d837279d6b56d',
        'info_dict': {
            'id': '296152',
            'title': 'Pulso activo | 18-11-2025',
            'description': 'Anxo, Noemí, Silvia e Estrella  comparten as sensacións da clase de Eddy.',
            'thumbnail': 'https://crtvg-bucket.flumotion.cloud/content_cards/a6bb7da6c8994b82bf961ac6cad1707b.png',
            'ext': 'mp4',

Review comment (security):
There is nothing useful we can do without this token, right? Then might as well make this fatal
```suggestion
        access_token = self._download_json(
            'https://www.agalega.gal/api/fetch-api/jwt/token', video_id,
            note='Downloading access token',
            data=json.dumps({
                'username': None,
                'password': None,
                'client': 'crtvg',
                'checkExistsCookies': False,
            }).encode())['access']
        return {'authorization': f'jwtok {access_token}'}
```
Also, how aggressively rate limited is this API, should we cache the token?


Code after:
class AGalegaBaseIE(InfoExtractor):
    _access_token = None

    @staticmethod
    def _jwt_is_expired(token):
        return jwt_decode_hs256(token)['exp'] - time.time() < 120

    def _refresh_access_token(self, video_id):
        AGalegaBaseIE._access_token = self._download_json(
            'https://www.agalega.gal/api/fetch-api/jwt/token', video_id,
            note='Downloading access token',
            data=json.dumps({
                'username': None,
                'password': None,
                'client': 'crtvg',
                'checkExistsCookies': False,
            }).encode())['access']

    def _call_api(self, endpoint, display_id, note, fatal=True, query=None):
        if not AGalegaBaseIE._access_token or self._jwt_is_expired(AGalegaBaseIE._access_token):
            self._refresh_access_token(endpoint)
        return self._download_json(
            f'https://api-agalega.interactvty.com/api/2.0/contents/{endpoint}', display_id,
            note=note, fatal=fatal, query=query,
            headers={'Authorization': f'jwtok {AGalegaBaseIE._access_token}'})


class AGalegaIE(AGalegaBaseIE):
    IE_NAME = 'agalega:videos'
    _VALID_URL = r'https?://(?:www\.)?agalega\.gal/videos/(?:detail/)?(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://www.agalega.gal/videos/288664-lr-ninguencheconta',
        'md5': '04533a66c5f863d08dd9724b11d1c223',
        'info_dict': {
            'id': '288664',
            'title': 'Roberto e Ángel Martín atenden consultas dos espectadores',
            'description': 'O cómico ademais fai un repaso dalgúns momentos da súa traxectoria profesional',
            'thumbnail': 'https://crtvg-bucket.flumotion.cloud/content_cards/2ef32c3b9f6249d9868fd8f11d389d3d.png',
            'ext': 'mp4',
        },
    }, {
        'url': 'https://www.agalega.gal/videos/detail/296152-pulso-activo-7',
        'md5': '26df7fdcf859f38ad92d837279d6b56d',
        'info_dict': {
            'id': '296152',
            'title': 'Pulso activo | 18-11-2025',
            'description': 'Anxo, Noemí, Silvia e Estrella  comparten as sensacións da clase de Eddy.',
            'thumbnail': 'https://crtvg-bucket.flumotion.cloud/content_cards/a6bb7da6c8994b82bf961ac6cad1707b.png',
            'ext': 'mp4',
        },
    }]