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

Code before:
import re

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    ExtractorError,
    int_or_none,
    str_or_none,
    traverse_obj,
    urlencode_postdata,
)


class FreeTvBaseIE(InfoExtractor):
    def _get_api_response(self, content_id, postdata, resource_type):
        response = self._download_json(
            'https://www.freetv.com/wordpress/wp-admin/admin-ajax.php',
            content_id, data=urlencode_postdata(postdata), fatal=True,
            note=f'Downloading {content_id} {resource_type} JSON')

        if response.get('data') is False:
            raise ExtractorError(f"Response doesn't contain {resource_type} data")

        return response

    def _extract_video(self, content_id, action='olyott_video_play'):
        api_response = self._get_api_response(
            content_id, resource_type='video',
            postdata={
                'action': action,
                'contentID': content_id,
            })

        video_id = traverse_obj(api_response, ('data', 'displayMeta', 'contentID'), expected_type=str)
        video_url = traverse_obj(api_response, ('data', 'displayMeta', 'streamURLVideo'), expected_type=str)

        if determine_ext(video_url) != 'm3u8':
            raise ExtractorError('Manifest was not found', video_id=video_id)

        formats, subtitles = self._extract_m3u8_formats_and_subtitles(video_url, video_id, 'mp4')
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': traverse_obj(api_response, ('data', 'displayMeta', 'title')),
            'description': traverse_obj(api_response, ('data', 'displayMeta', 'desc')),
            'formats': formats,

Review comment (refactor):
`Response doesn't contain ...` means nothing to the user. So I don't understand why this error message is needed (as opposed to the default error of `reponse['data']`. But if u r doing it, it should be 
```suggestion
        if not response.get('data'):
            raise ExtractorError(f"Response doesn't contain {resource_type} data")
```

Code after:
import itertools
import re

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    traverse_obj,
    urlencode_postdata,
)


class FreeTvBaseIE(InfoExtractor):
    def _get_api_response(self, content_id, resource_type, postdata):
        return self._download_json(
            'https://www.freetv.com/wordpress/wp-admin/admin-ajax.php',
            content_id, data=urlencode_postdata(postdata),
            note=f'Downloading {content_id} {resource_type} JSON')['data']


class FreeTvMoviesIE(FreeTvBaseIE):
    _VALID_URL = r'https?://(?:www\.)?freetv\.com/peliculas/(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'https://www.freetv.com/peliculas/atrapame-si-puedes/',
        'md5': 'dc62d5abf0514726640077cd1591aa92',
        'info_dict': {
            'id': '428021',
            'title': 'Atrápame Si Puedes',
            'description': 'md5:ca63bc00898aeb2f64ec87c6d3a5b982',
            'ext': 'mp4',
        }
    }, {
        'url': 'https://www.freetv.com/peliculas/monstruoso/',
        'md5': '509c15c68de41cb708d1f92d071f20aa',
        'info_dict': {
            'id': '377652',
            'title': 'Monstruoso',
            'description': 'md5:333fc19ee327b457b980e54a911ea4a3',
            'ext': 'mp4',
        }
    }]

    def _extract_video(self, content_id, action='olyott_video_play'):
        api_response = self._get_api_response(content_id, 'video', {
            'action': action,
            'contentID': content_id,
        })