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

Code before:
}


class AllstarBaseIE(InfoExtractor):
    @staticmethod
    def _parse_video_data(video_data):
        def _media_url_or_none(path):
            return urljoin('https://media.allstar.gg/', path)

        def _profile_url_or_none(path):
            return urljoin('https://allstar.gg/u/', path)

        return traverse_obj(video_data, {
            'id': ('_id', {str}),
            'display_id': ('shareId', {str}),
            'title': ('clipTitle', {str}),
            'url': ('clipLink', {_media_url_or_none}),
            'thumbnail': ('clipImageThumb', {_media_url_or_none}),
            'duration': ('clipLength', {int_or_none}),
            'filesize': ('clipSizeBytes', {int_or_none}),
            'timestamp': ('createdDate', {int_or_none}),
            'uploader': ('username', {str}),
            'uploader_id': ('user', '_id', {str}),
            'uploader_url': ('user', '_id', {_profile_url_or_none}),
            'view_count': ('views', {int_or_none}),
        })

    def _send_query(self, query, variables={}, path=(), video_id=None, note=None):
        response = self._download_json(
            'https://a1.allstar.gg/graphql', video_id, note=note,
            headers={'content-type': 'application/json'},
            data=json.dumps({'variables': variables, 'query': query}).encode())

        errors = response.get('errors')
        if errors:
            raise ExtractorError(errors, expected=True)

        return traverse_obj(response, path)


class AllstarIE(AllstarBaseIE):
    _VALID_URL = r'https?://(?:www\.)?allstar\.gg/(?P<type>(?:clip|montage))\?(?P=type)=(?P<id>[^/?#&]+)'

    _TESTS = [{
        'url': 'https://allstar.gg/clip?clip=64482c2da9eec30008a67d1b',
        'info_dict': {
            'id': '64482c2da9eec30008a67d1b',
            'title': '4K on Inferno',
            'url': 'md5:66befb5381eef0c9456026386c25fa55',
            'thumbnail': 'md5:33c520f681627826a95ac43b092ecd2b',
            'uploader': 'chrk.',

Review comment (refactor):
- since this method is being used to return video info dicts by both the video extractor and the playlist extractor, we need to set the info dict's `extractor` and `extractor_key` to the video extractor
- similarly, `webpage_url` should be set in this method instead of separately in the playlist extractor
- `timestamp` value was being extracted in milliseconds

```suggestion
    @staticmethod
    def _parse_video_data(video_data):
        def media_url_or_none(path):
            return urljoin('https://media.allstar.gg/', path)

        info = traverse_obj(video_data, {
            'id': ('_id', {str}),
            'display_id': ('shareId', {str}),
            'title': ('clipTitle', {str}),
            'url': ('clipLink', {media_url_or_none}),
            'thumbnail': ('clipImageThumb', {media_url_or_none}),
            'duration': ('clipLength', {functools.partial(int_or_none, scale=1000)}),
            'filesize': ('clipSizeBytes', {int_or_none}),
            'timestamp': ('createdDate', {int_or_none}),
            'uploader': ('username', {str}),
            'uploader_id': ('user', '_id', {str}),
            'view_count': ('views', {int_or_none}),
        })

        if info.get('id') and info.get('url'):
            basename = 'clip' if '/clips/' in info['url'] else 'montage'
            info_dict['webpage_url'] = f'https://allstar.gg/{basename}?{basename}={info["id"]}'

        info.update({
            'extractor_key': AllstarIE.ie_key(),
            'extractor': AllstarIE.IE_NAME,
            'uploader_url': urljoin('https://allstar.gg/u/', info.get('uploader_id')),
        })

        return info
```

Code after:
    }''' % (_FIELDS, _EXTRA_FIELDS),
}


class AllstarBaseIE(InfoExtractor):
    @staticmethod
    def _parse_video_data(video_data):
        def media_url_or_none(path):
            return urljoin('https://media.allstar.gg/', path)

        info = traverse_obj(video_data, {
            'id': ('_id', {str}),
            'display_id': ('shareId', {str}),
            'title': ('clipTitle', {str}),
            'url': ('clipLink', {media_url_or_none}),
            'thumbnails': (('clipImageThumb', 'clipImageSource'), {'url': {media_url_or_none}}),
            'duration': ('clipLength', {int_or_none}),
            'filesize': ('clipSizeBytes', {int_or_none}),
            'timestamp': ('createdDate', {functools.partial(int_or_none, scale=1000)}),
            'uploader': ('username', {str}),
            'uploader_id': ('user', '_id', {str}),
            'view_count': ('views', {int_or_none}),
        })

        if info.get('id') and info.get('url'):
            basename = 'clip' if '/clips/' in info['url'] else 'montage'
            info['webpage_url'] = f'https://allstar.gg/{basename}?{basename}={info["id"]}'

        info.update({
            'extractor_key': AllstarIE.ie_key(),
            'extractor': AllstarIE.IE_NAME,
            'uploader_url': urljoin('https://allstar.gg/u/', info.get('uploader_id')),
        })

        return info

    def _call_api(self, query, variables, path, video_id=None, note=None):
        response = self._download_json(
            'https://a1.allstar.gg/graphql', video_id, note=note,
            headers={'content-type': 'application/json'},
            data=json.dumps({'variables': variables, 'query': query}).encode())

        errors = traverse_obj(response, ('errors', ..., 'message', {str}))
        if errors:
            raise ExtractorError('; '.join(errors))

        return traverse_obj(response, path)


class AllstarIE(AllstarBaseIE):
    _VALID_URL = r'https?://(?:www\.)?allstar\.gg/(?P<type>(?:clip|montage))\?(?P=type)=(?P<id>[^/?#&]+)'