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

Code before:
        'url': 'https://asobistage.asobistore.jp/event/idolmaster_idolworld2023_goods/archive/live',
        'info_dict': {
            'id': 'idolmaster_idolworld2023_goods/archive/live/3aef7110',
            'title': 'asobistore_station_1020_serverREC',
            'live_status': 'was_live',
            'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
        },
        'params': {
            'skip_download': True,
            'ignore_no_formats_error': True,
        },
    }, {
        'url': 'https://asobistage.asobistore.jp/event/ijigenfes_utagassen/player/day1',
        'only_matching': True,
    }]

    def _check_login(self, video_id):
        check_login_json = self._download_json(
            'https://asobistage-api.asobistore.jp/api/v1/check_login', video_id, expected_status=(400),
            note='Checking login status', errnote='Unable to check login status')
        error = traverse_obj(check_login_json, ('payload', 'error_message'), ('error'), get_all=False)

        if not error:
            return
        elif error == 'notlogin':
            self.raise_login_required(metadata_available=False)
        else:
            raise ExtractorError(f'Unknown error: {error}')

    def _get_token(self, video_id):
        return self._search_regex(r'\"([^"]+)\"', self._download_webpage(
            'https://asobistage-api.asobistore.jp/api/v1/vspf/token', video_id,
            note='Getting token', errnote='Unable to get token'),
            name="token")

    def _get_owned_tickets(self, video_id):
        for url, name in [
            ('https://asobistage-api.asobistore.jp/api/v1/purchase_history/list', 'ticket purchase history'),
            ('https://asobistage-api.asobistore.jp/api/v1/serialcode/list', 'redemption history'),
        ]:
            for id in traverse_obj(self._download_json(
                    url, video_id, note=f'Downloading {name}', errnote=f'Unable to download {name}'),
                    ('payload', 'value', ..., 'digital_product_id', {str})):
                yield id

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video_type_name = self._match_valid_url(url).group('type')
        webpage = self._download_webpage(url, video_id)

        self._check_login(video_id)

Review comment (suggestion):
`metadata_available=False` is the default
```suggestion
            self.raise_login_required()
```

Code after:
        'info_dict': {
            'id': 'idolmaster_idolworld2023_goods/archive/live',
            'title': 'md5:378510b6e830129d505885908bd6c576',
            'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
        },
        'playlist_count': 1,
        'playlist': [{
            'info_dict': {
                'id': '3aef7110',
                'ext': 'mp4',
                'title': 'asobistore_station_1020_serverREC',
                'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
            },
        }],
    }, {
        'url': 'https://asobistage.asobistore.jp/event/sidem_fclive_bpct/archive/premium_hc',
        'playlist_count': 4,
        'info_dict': {
            'id': 'sidem_fclive_bpct/archive/premium_hc',
            'title': '315 Production presents F＠NTASTIC COMBINATION LIVE ～BRAINPOWER!!～/～CONNECTIME!!!!～',
            'thumbnail': r're:^https?://[\w.-]+/\w+/\w+',
        },
    }, {
        'url': 'https://asobistage.asobistore.jp/event/ijigenfes_utagassen/player/day1',
        'only_matching': True,
    }]

    _API_HOST = 'https://asobistage-api.asobistore.jp'
    _HEADERS = {}
    _is_logged_in = False

    @functools.cached_property
    def _owned_tickets(self):
        owned_tickets = set()
        if not self._is_logged_in:
            return owned_tickets

        for path, name in [
            ('api/v1/purchase_history/list', 'ticket purchase history'),
            ('api/v1/serialcode/list', 'redemption history'),
        ]:
            response = self._download_json(
                f'{self._API_HOST}/{path}', None, f'Downloading {name}',
                f'Unable to download {name}', expected_status=400)
            if traverse_obj(response, ('payload', 'error_message'), 'error') == 'notlogin':
                self._is_logged_in = False
                break
            owned_tickets.update(
                traverse_obj(response, ('payload', 'value', ..., 'digital_product_id', {str_or_none})))

        return owned_tickets