4 from .common
import InfoExtractor
5 from .youtube
import YoutubeIE
14 from ..utils
.traversal
import traverse_obj
17 class BoostyIE(InfoExtractor
):
18 _VALID_URL
= r
'https?://(?:www\.)?boosty\.to/(?P<user>[^/#?]+)/posts/(?P<post_id>[^/#?]+)'
21 'url': 'https://boosty.to/kuplinov/posts/e55d050c-e3bb-4873-a7db-ac7a49b40c38',
23 'id': 'd7473824-352e-48e2-ae53-d4aa39459968',
25 'channel': 'Kuplinov',
26 'channel_id': '7958701',
27 'timestamp': 1655031975,
28 'upload_date': '20220612',
29 'release_timestamp': 1655049000,
30 'release_date': '20220612',
31 'modified_timestamp': 1668680993,
32 'modified_date': '20221117',
33 'tags': ['куплинов', 'phasmophobia'],
38 'thumbnail': r
're:^https://i\.mycdn\.me/videoPreview\?',
42 'url': 'https://boosty.to/maddyson/posts/0c652798-3b35-471f-8b48-a76a0b28736f',
44 'id': '0c652798-3b35-471f-8b48-a76a0b28736f',
45 'title': 'то что не пропустил юта6',
46 'channel': 'Илья Давыдов',
47 'channel_id': '6808257',
48 'timestamp': 1694017040,
49 'upload_date': '20230906',
50 'release_timestamp': 1694017040,
51 'release_date': '20230906',
52 'modified_timestamp': 1694071178,
53 'modified_date': '20230907',
59 'id': 'cc325a9f-a563-41c6-bf47-516c1b506c9a',
60 'title': 'то что не пропустил юта6',
61 'channel': 'Илья Давыдов',
62 'channel_id': '6808257',
63 'timestamp': 1694017040,
64 'upload_date': '20230906',
65 'release_timestamp': 1694017040,
66 'release_date': '20230906',
67 'modified_timestamp': 1694071178,
68 'modified_date': '20230907',
73 'thumbnail': r
're:^https://i\.mycdn\.me/videoPreview\?',
77 'id': 'd07b0a72-9493-4512-b54e-55ce468fd4b7',
78 'title': 'то что не пропустил юта6',
79 'channel': 'Илья Давыдов',
80 'channel_id': '6808257',
81 'timestamp': 1694017040,
82 'upload_date': '20230906',
83 'release_timestamp': 1694017040,
84 'release_date': '20230906',
85 'modified_timestamp': 1694071178,
86 'modified_date': '20230907',
91 'thumbnail': r
're:^https://i\.mycdn\.me/videoPreview\?',
95 'id': '4a3bba32-78c8-422a-9432-2791aff60b42',
96 'title': 'то что не пропустил юта6',
97 'channel': 'Илья Давыдов',
98 'channel_id': '6808257',
99 'timestamp': 1694017040,
100 'upload_date': '20230906',
101 'release_timestamp': 1694017040,
102 'release_date': '20230906',
103 'modified_timestamp': 1694071178,
104 'modified_date': '20230907',
109 'thumbnail': r
're:^https://i\.mycdn\.me/videoPreview\?',
113 # single external video (youtube)
114 'url': 'https://boosty.to/denischuzhoy/posts/6094a487-bcec-4cf8-a453-43313b463c38',
117 'title': 'Послание Президента Федеральному Собранию | Класс народа',
118 'upload_date': '20210425',
119 'channel': 'Денис Чужой',
125 'thumbnail': r
're:^https://i\.ytimg\.com/',
127 'availability': 'public',
129 'channel_follower_count': int,
130 'channel_id': 'UCCzVNbWZfYpBfyofCCUD_0w',
131 'channel_is_verified': bool,
132 'channel_url': r
're:^https://www\.youtube\.com/',
133 'comment_count': int,
135 'heatmap': 'count:100',
137 'playable_in_embed': bool,
140 'uploader_url': r
're:^https://www\.youtube\.com/',
144 _MP4_TYPES
= ('tiny', 'lowest', 'low', 'medium', 'high', 'full_hd', 'quad_hd', 'ultra_hd')
146 def _extract_formats(self
, player_urls
, video_id
):
148 quality
= qualities(self
._MP
4_TYPES
)
149 for player_url
in traverse_obj(player_urls
, lambda _
, v
: url_or_none(v
['url'])):
150 url
= player_url
['url']
151 format_type
= player_url
.get('type')
152 if format_type
in ('hls', 'hls_live', 'live_ondemand_hls', 'live_playback_hls'):
153 formats
.extend(self
._extract
_m
3u8_formats
(url
, video_id
, m3u8_id
='hls', fatal
=False))
154 elif format_type
in ('dash', 'dash_live', 'live_playback_dash'):
155 formats
.extend(self
._extract
_mpd
_formats
(url
, video_id
, mpd_id
='dash', fatal
=False))
156 elif format_type
in self
._MP
4_TYPES
:
160 'format_id': format_type
,
161 'quality': quality(format_type
),
164 self
.report_warning(f
'Unknown format type: {format_type!r}')
167 def _real_extract(self
, url
):
168 user
, post_id
= self
._match
_valid
_url
(url
).group('user', 'post_id')
171 auth_cookie
= self
._get
_cookies
('https://boosty.to/').get('auth')
172 if auth_cookie
is not None:
174 auth_data
= json
.loads(urllib
.parse
.unquote(auth_cookie
.value
))
175 auth_headers
['Authorization'] = f
'Bearer {auth_data["accessToken"]}'
176 except (json
.JSONDecodeError
, KeyError):
177 self
.report_warning(f
'Failed to extract token from auth cookie{bug_reports_message()}')
179 post
= self
._download
_json
(
180 f
'https://api.boosty.to/v1/blog/{user}/post/{post_id}', post_id
,
181 note
='Downloading post data', errnote
='Unable to download post data', headers
=auth_headers
)
183 post_title
= post
.get('title')
185 self
.report_warning('Unable to extract post title. Falling back to parsing html page')
186 webpage
= self
._download
_webpage
(url
, video_id
=post_id
)
187 post_title
= self
._og
_search
_title
(webpage
, default
=None) or self
._html
_extract
_title
(webpage
)
191 **traverse_obj(post
, {
192 'channel': ('user', 'name', {str}
),
193 'channel_id': ('user', 'id', {str_or_none}
),
194 'timestamp': ('createdAt', {int_or_none}
),
195 'release_timestamp': ('publishTime', {int_or_none}
),
196 'modified_timestamp': ('updatedAt', {int_or_none}
),
197 'tags': ('tags', ..., 'title', {str}
),
198 'like_count': ('count', 'likes', {int_or_none}
),
202 for item
in traverse_obj(post
, ('data', ..., {dict}
)):
203 item_type
= item
.get('type')
204 if item_type
== 'video' and url_or_none(item
.get('url')):
205 entries
.append(self
.url_result(item
['url'], YoutubeIE
))
206 elif item_type
== 'ok_video':
207 video_id
= item
.get('id') or post_id
210 'formats': self
._extract
_formats
(item
.get('playerUrls'), video_id
),
212 **traverse_obj(item
, {
213 'title': ('title', {str}
),
214 'duration': ('duration', {int_or_none}
),
215 'view_count': ('viewsCounter', {int_or_none}
),
216 'thumbnail': (('previewUrl', 'defaultPreview'), {url_or_none}
),
219 if not entries
and not post
.get('hasAccess'):
220 self
.raise_login_required('This post requires a subscription', metadata_available
=True)
222 raise ExtractorError('No videos found', expected
=True)
223 if len(entries
) == 1:
225 return self
.playlist_result(entries
, post_id
, post_title
, **common_metadata
)