Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ie/asobistage] Add extractor (#8735)
Authored by: pzhlkj6612
- Loading branch information
1 parent
e803250
commit 0284f1f
Showing
2 changed files
with
155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import functools | ||
|
||
from .common import InfoExtractor | ||
from ..utils import str_or_none, url_or_none | ||
from ..utils.traversal import traverse_obj | ||
|
||
|
||
class AsobiStageIE(InfoExtractor): | ||
IE_DESC = 'ASOBISTAGE (アソビステージ)' | ||
_VALID_URL = r'https?://asobistage\.asobistore\.jp/event/(?P<id>(?P<event>\w+)/(?P<type>archive|player)/(?P<slug>\w+))(?:[?#]|$)' | ||
_TESTS = [{ | ||
'url': 'https://asobistage.asobistore.jp/event/315passionhour_2022summer/archive/frame', | ||
'info_dict': { | ||
'id': '315passionhour_2022summer/archive/frame', | ||
'title': '315プロダクションプレゼンツ 315パッションアワー!!!', | ||
'thumbnail': r're:^https?://[\w.-]+/\w+/\w+', | ||
}, | ||
'playlist_count': 1, | ||
'playlist': [{ | ||
'info_dict': { | ||
'id': 'edff52f2', | ||
'ext': 'mp4', | ||
'title': '315passion_FRAME_only', | ||
'thumbnail': r're:^https?://[\w.-]+/\w+/\w+', | ||
}, | ||
}], | ||
}, { | ||
'url': 'https://asobistage.asobistore.jp/event/idolmaster_idolworld2023_goods/archive/live', | ||
'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 | ||
|
||
def _get_available_channel_id(self, channel): | ||
channel_id = traverse_obj(channel, ('chennel_vspf_id', {str})) | ||
if not channel_id: | ||
return None | ||
# if rights_type_id == 6, then 'No conditions (no login required - non-members are OK)' | ||
if traverse_obj(channel, ('viewrights', lambda _, v: v['rights_type_id'] == 6)): | ||
return channel_id | ||
available_tickets = traverse_obj(channel, ( | ||
'viewrights', ..., ('tickets', 'serialcodes'), ..., 'digital_product_id', {str_or_none})) | ||
if not self._owned_tickets.intersection(available_tickets): | ||
self.report_warning( | ||
f'You are not a ticketholder for "{channel.get("channel_name") or channel_id}"') | ||
return None | ||
return channel_id | ||
|
||
def _real_initialize(self): | ||
if self._get_cookies(self._API_HOST): | ||
self._is_logged_in = True | ||
token = self._download_json( | ||
f'{self._API_HOST}/api/v1/vspf/token', None, 'Getting token', 'Unable to get token') | ||
self._HEADERS['Authorization'] = f'Bearer {token}' | ||
|
||
def _real_extract(self, url): | ||
video_id, event, type_, slug = self._match_valid_url(url).group('id', 'event', 'type', 'slug') | ||
video_type = {'archive': 'archives', 'player': 'broadcasts'}[type_] | ||
webpage = self._download_webpage(url, video_id) | ||
event_data = traverse_obj( | ||
self._search_nextjs_data(webpage, video_id, default='{}'), | ||
('props', 'pageProps', 'eventCMSData', { | ||
'title': ('event_name', {str}), | ||
'thumbnail': ('event_thumbnail_image', {url_or_none}), | ||
})) | ||
|
||
available_channels = traverse_obj(self._download_json( | ||
f'https://asobistage.asobistore.jp/cdn/v101/events/{event}/{video_type}.json', | ||
video_id, 'Getting channel list', 'Unable to get channel list'), ( | ||
video_type, lambda _, v: v['broadcast_slug'] == slug, | ||
'channels', lambda _, v: v['chennel_vspf_id'] != '00000')) | ||
|
||
entries = [] | ||
for channel_id in traverse_obj(available_channels, (..., {self._get_available_channel_id})): | ||
if video_type == 'archives': | ||
channel_json = self._download_json( | ||
f'https://survapi.channel.or.jp/proxy/v1/contents/{channel_id}/get_by_cuid', channel_id, | ||
'Getting archive channel info', 'Unable to get archive channel info', fatal=False, | ||
headers=self._HEADERS) | ||
channel_data = traverse_obj(channel_json, ('ex_content', { | ||
'm3u8_url': 'streaming_url', | ||
'title': 'title', | ||
'thumbnail': ('thumbnail', 'url'), | ||
})) | ||
else: # video_type == 'broadcasts' | ||
channel_json = self._download_json( | ||
f'https://survapi.channel.or.jp/ex/events/{channel_id}', channel_id, | ||
'Getting live channel info', 'Unable to get live channel info', fatal=False, | ||
headers=self._HEADERS, query={'embed': 'channel'}) | ||
channel_data = traverse_obj(channel_json, ('data', { | ||
'm3u8_url': ('Channel', 'Custom_live_url'), | ||
'title': 'Name', | ||
'thumbnail': 'Poster_url', | ||
})) | ||
|
||
entries.append({ | ||
'id': channel_id, | ||
'title': channel_data.get('title'), | ||
'formats': self._extract_m3u8_formats(channel_data.get('m3u8_url'), channel_id, fatal=False), | ||
'is_live': video_type == 'broadcasts', | ||
'thumbnail': url_or_none(channel_data.get('thumbnail')), | ||
}) | ||
|
||
if not self._is_logged_in and not entries: | ||
self.raise_login_required() | ||
|
||
return self.playlist_result(entries, video_id, **event_data) |