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

Code before:
            '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',
        },
    },
        {
        'url': 'https://www.agalega.gal/videos/category/27035-galician-friki',
        'info_dict': {
            'id': '27035',
            'title': 'Galician Friki',
            'description': 'md5:68279845eea70ba3b4324bd96e3aac30',
        },
        'playlist_count': 7,
    },
        {
        'url': 'https://www.agalega.gal/videos/category/17175-na-gloria?cat=17176',
        'info_dict': {
            'id': '17175',
            'title': 'Na Gloria',
            'description': 'Na parroquia da Gloria vivíase ben... ata que chegaron unha madrileña e un andaluz e abriron un hotel "refinado".\n',
        },
        'playlist_count': 17,
    },
    ]

    def getAPIToken(self, videoid: str) -> dict[str, str] | None:
        tokenUrl = 'https://www.agalega.gal/api/fetch-api/jwt/token'

        bodyData = json.dumps({
            'username': None,
            'password': None,
            'client': 'crtvg',
            'checkExistsCookies': False,
        }).encode()

        tokenRaw: dict[str, str] | None = self._download_json(tokenUrl, videoid, data=bodyData)

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

    def singleVideoExtraction(self, videoid: str, auth_header: dict[str, str]) -> dict[str, str] | None:
        infoUrl = f'https://api-agalega.interactvty.com/api/2.0/contents/content/{videoid}/?optional_fields=image,is_premium,short_description,has_subtitle'
        mediaUrl = f'https://api-agalega.interactvty.com/api/2.0/contents/content_resources/{videoid}/?optional_fields=media_url'
        infoRaw = self._download_json(infoUrl, videoid, headers=auth_header)
        mediaRaw = self._download_json(mediaUrl, videoid, headers=auth_header)


Review comment (refactor):
This can be moved in to a base class

As a side note, yt-dlp extractor code currently does not use any typing, as generally sites could be sending us anything. Please remove it.
Please also use `snake_case`, instead of `camelCase` throughout the extractor.

Code after:
            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',
        },
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        content_data = self._call_api(
            f'content/{video_id}/', video_id, note='Downloading content data', fatal=False,
            query={
                'optional_fields': 'image,is_premium,short_description,has_subtitle',
            })
        resource_data = self._call_api(
            f'content_resources/{video_id}/', video_id, note='Downloading resource data',
            query={
                'optional_fields': 'media_url',
            })

        formats = []
        subtitles = {}
        for m3u8_url in traverse_obj(resource_data, ('results', ..., 'media_url', {url_or_none})):
            fmts, subs = self._extract_m3u8_formats_and_subtitles(
                m3u8_url, video_id, ext='mp4', m3u8_id='hls')
            formats.extend(fmts)
            self._merge_subtitles(subs, target=subtitles)

        return {
            'id': video_id,