1
from .common
import InfoExtractor
12 class CybraryBaseIE(InfoExtractor
):
13 _API_KEY
= 'AIzaSyCX9ru6j70PX2My1Eq6Q1zoMAhuTdXlzSw'
15 'course': 'https://app.cybrary.it/courses/api/catalog/browse/course/{}',
16 'course_enrollment': 'https://app.cybrary.it/courses/api/catalog/{}/enrollment',
17 'enrollment': 'https://app.cybrary.it/courses/api/enrollment/{}',
18 'launch': 'https://app.cybrary.it/courses/api/catalog/{}/launch',
19 'vimeo_oembed': 'https://vimeo.com/api/oembed.json?url=https://vimeo.com/{}',
21 _NETRC_MACHINE
= 'cybrary'
24 def _perform_login(self
, username
, password
):
25 CybraryBaseIE
._TOKEN
= self
._download
_json
(
26 f
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={self._API_KEY}',
27 None, data
=urlencode_postdata({'email': username, 'password': password, 'returnSecureToken': True}
),
28 note
='Logging in')['idToken']
30 def _real_initialize(self
):
32 self
.raise_login_required(method
='password')
34 def _call_api(self
, endpoint
, item_id
):
35 return self
._download
_json
(
36 self
._ENDPOINTS
[endpoint
].format(item_id
), item_id
,
37 note
=f
'Downloading {endpoint} JSON metadata',
38 headers
={'Authorization': f'Bearer {self._TOKEN}
'})
40 def _get_vimeo_id(self, activity_id):
41 launch_api = self._call_api('launch
', activity_id)
43 if launch_api.get('url
'):
44 return self._search_regex(r'https?
://player\
.vimeo\
.com
/video
/(?P
<vimeo_id
>[0-9]+)', launch_api['url
'], 'vimeo_id
')
45 return traverse_obj(launch_api, ('vendor_data
', 'content
', ..., 'videoId
'), get_all=False)
48 class CybraryIE(CybraryBaseIE):
49 _VALID_URL = r'https?
://app
.cybrary
.it
/immersive
/(?P
<enrollment
>[0-9]+)/activity
/(?P
<id>[0-9]+)'
51 'url
': 'https
://app
.cybrary
.it
/immersive
/12487950/activity
/63102',
52 'md5
': '9ae12d37e555cb2ed554223a71a701d0
',
56 'title
': 'Getting Started
',
57 'thumbnail
': 'https
://i
.vimeocdn
.com
/video
/1301817996-76a268f0c56cff18a5cecbbdc44131eb9dda0c80eb0b3a036_1280
',
59 'uploader_url
': 'https
://vimeo
.com
/user30867300
',
61 'uploader_id
': 'user30867300
',
62 'series
': 'Cybrary Orientation
',
63 'uploader
': 'Cybrary
',
64 'chapter
': 'Cybrary Orientation Series
',
67 'expected_warnings
': ['No authenticators
for vimeo
']
69 'url
': 'https
://app
.cybrary
.it
/immersive
/12747143/activity
/52686',
70 'md5
': '62f26547dccc59c44363e2a13d4ad08d
',
74 'title
': 'Azure Virtual Network IP Addressing
',
75 'thumbnail
': 'https
://i
.vimeocdn
.com
/video
/936667051-1647ace66c627d4a2382185e0dae8deb830309bfddd53f8b2367b2f91e92ed0e
-d_1280
',
77 'uploader_url
': 'https
://vimeo
.com
/user30867300
',
79 'uploader_id
': 'user30867300
',
80 'series
': 'AZ
-500: Microsoft Azure Security Technologies
',
81 'uploader
': 'Cybrary
',
82 'chapter
': 'Implement Network Security
',
85 'expected_warnings
': ['No authenticators
for vimeo
']
88 def _real_extract(self, url):
89 activity_id, enrollment_id = self._match_valid_url(url).group('id', 'enrollment
')
90 course = self._call_api('enrollment
', enrollment_id)['content
']
91 activity = traverse_obj(course, ('learning_modules
', ..., 'activities
', lambda _, v: int(activity_id) == v['id']), get_all=False)
93 if activity.get('type') not in ['Video Activity
', 'Lesson Activity
']:
94 raise ExtractorError('The activity
is not a video
', expected=True)
96 module = next((m for m in course.get('learning_modules
') or []
97 if int(activity_id) in traverse_obj(m, ('activities
', ..., 'id') or [])), None)
99 vimeo_id = self._get_vimeo_id(activity_id)
102 '_type
': 'url_transparent
',
103 'series
': traverse_obj(course, ('content_description
', 'title
')),
104 'series_id
': str_or_none(traverse_obj(course, ('content_description
', 'id'))),
106 'chapter
': module.get('title
'),
107 'chapter_id
': str_or_none(module.get('id')),
108 'title
': activity.get('title
'),
109 'url
': smuggle_url(f'https
://player
.vimeo
.com
/video
/{vimeo_id}
', {'http_headers': {'Referer': 'https://api.cybrary.it'}})
113 class CybraryCourseIE(CybraryBaseIE):
114 _VALID_URL = r'https
://app
.cybrary
.it
/browse
/course
/(?P
<id>[\w
-]+)/?
(?
:$|
[#?])'
116 'url': 'https://app.cybrary.it/browse/course/az-500-microsoft-azure-security-technologies',
119 'title': 'AZ-500: Microsoft Azure Security Technologies',
120 'description': 'md5:69549d379c0fc1dec92926d4e8b6fbd4'
124 'url': 'https://app.cybrary.it/browse/course/cybrary-orientation',
127 'title': 'Cybrary Orientation',
128 'description': 'md5:9e69ff66b32fe78744e0ad4babe2e88e'
133 def _real_extract(self
, url
):
134 course_id
= self
._match
_id
(url
)
135 course
= self
._call
_api
('course', course_id
)
136 enrollment_info
= self
._call
_api
('course_enrollment', course
['id'])
138 entries
= [self
.url_result(
139 f
'https://app.cybrary.it/immersive/{enrollment_info["id"]}/activity/{activity["id"]}')
140 for activity
in traverse_obj(course
, ('content_item', 'learning_modules', ..., 'activities', ...))]
142 return self
.playlist_result(
144 traverse_obj(course
, ('content_item', 'id'), expected_type
=str_or_none
),
145 course
.get('title'), course
.get('short_description'))