3 from .common
import InfoExtractor
4 from ..utils
import str_or_none
, url_or_none
5 from ..utils
.traversal
import traverse_obj
8 class AsobiStageIE(InfoExtractor
):
9 IE_DESC
= 'ASOBISTAGE (アソビステージ)'
10 _VALID_URL
= r
'https?://asobistage\.asobistore\.jp/event/(?P<id>(?P<event>\w+)/(?P<type>archive|player)/(?P<slug>\w+))(?:[?#]|$)'
12 'url': 'https://asobistage.asobistore.jp/event/315passionhour_2022summer/archive/frame',
14 'id': '315passionhour_2022summer/archive/frame',
15 'title': '315プロダクションプレゼンツ 315パッションアワー!!!',
16 'thumbnail': r
're:^https?://[\w.-]+/\w+/\w+',
23 'title': '315passion_FRAME_only',
24 'thumbnail': r
're:^https?://[\w.-]+/\w+/\w+',
28 'url': 'https://asobistage.asobistore.jp/event/idolmaster_idolworld2023_goods/archive/live',
30 'id': 'idolmaster_idolworld2023_goods/archive/live',
31 'title': 'md5:378510b6e830129d505885908bd6c576',
32 'thumbnail': r
're:^https?://[\w.-]+/\w+/\w+',
39 'title': 'asobistore_station_1020_serverREC',
40 'thumbnail': r
're:^https?://[\w.-]+/\w+/\w+',
44 'url': 'https://asobistage.asobistore.jp/event/sidem_fclive_bpct/archive/premium_hc',
47 'id': 'sidem_fclive_bpct/archive/premium_hc',
48 'title': '315 Production presents F@NTASTIC COMBINATION LIVE ~BRAINPOWER!!~/~CONNECTIME!!!!~',
49 'thumbnail': r
're:^https?://[\w.-]+/\w+/\w+',
52 'url': 'https://asobistage.asobistore.jp/event/ijigenfes_utagassen/player/day1',
53 'only_matching': True,
56 _API_HOST
= 'https://asobistage-api.asobistore.jp'
60 @functools.cached_property
61 def _owned_tickets(self
):
63 if not self
._is
_logged
_in
:
67 ('api/v1/purchase_history/list', 'ticket purchase history'),
68 ('api/v1/serialcode/list', 'redemption history'),
70 response
= self
._download
_json
(
71 f
'{self._API_HOST}/{path}', None, f
'Downloading {name}',
72 f
'Unable to download {name}', expected_status
=400)
73 if traverse_obj(response
, ('payload', 'error_message'), 'error') == 'notlogin':
74 self
._is
_logged
_in
= False
77 traverse_obj(response
, ('payload', 'value', ..., 'digital_product_id', {str_or_none}
)))
81 def _get_available_channel_id(self
, channel
):
82 channel_id
= traverse_obj(channel
, ('chennel_vspf_id', {str}
))
85 # if rights_type_id == 6, then 'No conditions (no login required - non-members are OK)'
86 if traverse_obj(channel
, ('viewrights', lambda _
, v
: v
['rights_type_id'] == 6)):
88 available_tickets
= traverse_obj(channel
, (
89 'viewrights', ..., ('tickets', 'serialcodes'), ..., 'digital_product_id', {str_or_none}
))
90 if not self
._owned
_tickets
.intersection(available_tickets
):
92 f
'You are not a ticketholder for "{channel.get("channel_name") or channel_id}"')
96 def _real_initialize(self
):
97 if self
._get
_cookies
(self
._API
_HOST
):
98 self
._is
_logged
_in
= True
99 token
= self
._download
_json
(
100 f
'{self._API_HOST}/api/v1/vspf/token', None, 'Getting token', 'Unable to get token')
101 self
._HEADERS
['Authorization'] = f
'Bearer {token}'
103 def _real_extract(self
, url
):
104 webpage
, urlh
= self
._download
_webpage
_handle
(url
, self
._match
_id
(url
))
105 video_id
, event
, type_
, slug
= self
._match
_valid
_url
(urlh
.url
).group('id', 'event', 'type', 'slug')
106 video_type
= {'archive': 'archives', 'player': 'broadcasts'}[type_
]
108 event_data
= traverse_obj(
109 self
._search
_nextjs
_data
(webpage
, video_id
, default
={}),
110 ('props', 'pageProps', 'eventCMSData', {
111 'title': ('event_name', {str}
),
112 'thumbnail': ('event_thumbnail_image', {url_or_none}
),
115 available_channels
= traverse_obj(self
._download
_json
(
116 f
'https://asobistage.asobistore.jp/cdn/v101/events/{event}/{video_type}.json',
117 video_id
, 'Getting channel list', 'Unable to get channel list'), (
118 video_type
, lambda _
, v
: v
['broadcast_slug'] == slug
,
119 'channels', lambda _
, v
: v
['chennel_vspf_id'] != '00000'))
122 for channel_id
in traverse_obj(available_channels
, (..., {self
._get
_available
_channel
_id
})):
123 if video_type
== 'archives':
124 channel_json
= self
._download
_json
(
125 f
'https://survapi.channel.or.jp/proxy/v1/contents/{channel_id}/get_by_cuid', channel_id
,
126 'Getting archive channel info', 'Unable to get archive channel info', fatal
=False,
127 headers
=self
._HEADERS
)
128 channel_data
= traverse_obj(channel_json
, ('ex_content', {
129 'm3u8_url': 'streaming_url',
131 'thumbnail': ('thumbnail', 'url'),
133 else: # video_type == 'broadcasts'
134 channel_json
= self
._download
_json
(
135 f
'https://survapi.channel.or.jp/ex/events/{channel_id}', channel_id
,
136 'Getting live channel info', 'Unable to get live channel info', fatal
=False,
137 headers
=self
._HEADERS
, query
={'embed': 'channel'})
138 channel_data
= traverse_obj(channel_json
, ('data', {
139 'm3u8_url': ('Channel', 'Custom_live_url'),
141 'thumbnail': 'Poster_url',
146 'title': channel_data
.get('title'),
147 'formats': self
._extract
_m
3u8_formats
(channel_data
.get('m3u8_url'), channel_id
, fatal
=False),
148 'is_live': video_type
== 'broadcasts',
149 'thumbnail': url_or_none(channel_data
.get('thumbnail')),
152 if not self
._is
_logged
_in
and not entries
:
153 self
.raise_login_required()
155 return self
.playlist_result(entries
, video_id
, **event_data
)