udemy-userAPI 0.3.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- animation_consoles/__init__.py +1 -0
- animation_consoles/animation.py +64 -0
- ffmpeg_for_python/__config__.py +118 -0
- ffmpeg_for_python/__init__.py +8 -0
- ffmpeg_for_python/__utils.py +78 -0
- ffmpeg_for_python/__version__.py +6 -0
- ffmpeg_for_python/exeptions.py +91 -0
- ffmpeg_for_python/ffmpeg.py +203 -0
- m3u8_analyzer/M3u8Analyzer.py +807 -0
- m3u8_analyzer/__init__.py +7 -0
- m3u8_analyzer/__version__.py +1 -0
- m3u8_analyzer/exeptions.py +82 -0
- udemy_userAPI/.cache/.udemy_userAPI +0 -0
- udemy_userAPI/__init__.py +7 -0
- udemy_userAPI/__version__.py +6 -0
- udemy_userAPI/api.py +691 -0
- udemy_userAPI/authenticate.py +311 -0
- udemy_userAPI/bultins.py +495 -0
- udemy_userAPI/exeptions.py +22 -0
- udemy_userAPI/mpd_analyzer/__init__.py +3 -0
- udemy_userAPI/mpd_analyzer/bin.wvd +0 -0
- udemy_userAPI/mpd_analyzer/mpd_parser.py +224 -0
- udemy_userAPI/sections.py +117 -0
- udemy_userAPI/udemy.py +93 -0
- udemy_userAPI-0.3.2.dist-info/LICENSE +21 -0
- udemy_userAPI-0.3.2.dist-info/METADATA +34 -0
- udemy_userAPI-0.3.2.dist-info/RECORD +29 -0
- udemy_userAPI-0.3.2.dist-info/WHEEL +5 -0
- udemy_userAPI-0.3.2.dist-info/top_level.txt +1 -0
udemy_userAPI/bultins.py
ADDED
@@ -0,0 +1,495 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Any
|
3
|
+
import requests
|
4
|
+
from .api import get_links, remove_tag, parser_chapers, extract_files, HEADERS_USER, assets_infor, get_add_files, \
|
5
|
+
get_files_aule, get_external_liks, extract, get_pssh, organize_streams, get_mpd_file, get_highest_resolution
|
6
|
+
from .sections import get_course_infor
|
7
|
+
from .mpd_analyzer import MPDParser
|
8
|
+
from .exeptions import LoginException
|
9
|
+
|
10
|
+
|
11
|
+
class DRM:
|
12
|
+
def __init__(self, license_token: str, get_media_sources: list):
|
13
|
+
"""
|
14
|
+
Inicializa o objeto DRM.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
license_token (str): O token de licença.
|
18
|
+
get_media_sources (list): A lista de fontes de mídia.
|
19
|
+
"""
|
20
|
+
self.__mpd_file_path = None
|
21
|
+
self.__token = license_token
|
22
|
+
self.__dash_url = organize_streams(streams=get_media_sources).get('dash', {})
|
23
|
+
if not license_token or get_media_sources:
|
24
|
+
return
|
25
|
+
|
26
|
+
def get_key_for_lesson(self):
|
27
|
+
"""
|
28
|
+
Obtém as chaves para a aula.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
As chaves da aula ou None se não for possível obtê-las.
|
32
|
+
"""
|
33
|
+
try:
|
34
|
+
if self.__dash_url:
|
35
|
+
self.__mpd_file_path = get_mpd_file(mpd_url=self.__dash_url[0].get('src'))
|
36
|
+
parser = MPDParser(mpd_content=self.__mpd_file_path)
|
37
|
+
resolutions = get_highest_resolution(parser.get_all_video_resolutions())
|
38
|
+
parser.set_selected_resolution(resolution=resolutions)
|
39
|
+
init_url = parser.get_selected_video_init_url()
|
40
|
+
if init_url:
|
41
|
+
pssh = get_pssh(init_url=init_url)
|
42
|
+
if pssh:
|
43
|
+
keys = extract(pssh=pssh, license_token=self.__token)
|
44
|
+
if keys:
|
45
|
+
return keys
|
46
|
+
else:
|
47
|
+
return None
|
48
|
+
else:
|
49
|
+
return None
|
50
|
+
else:
|
51
|
+
return None
|
52
|
+
except Exception as e:
|
53
|
+
raise Exception(f"Não foi possível obter as chaves!\n{e}")
|
54
|
+
|
55
|
+
|
56
|
+
class Files:
|
57
|
+
def __init__(self, files: list[dict], id_course):
|
58
|
+
"""
|
59
|
+
Inicializa o objeto Files.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
files (list[dict]): Lista de dicionários contendo os dados dos arquivos.
|
63
|
+
id_course: ID do curso.
|
64
|
+
"""
|
65
|
+
self.__data = files
|
66
|
+
self.__id_course = id_course
|
67
|
+
|
68
|
+
@property
|
69
|
+
def get_download_url(self) -> dict[str, Any | None] | list[dict[str, Any | None]]:
|
70
|
+
"""
|
71
|
+
Obtém a URL de download de um arquivo quando disponível.
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
dict[str, Any | None] | list[dict[str, Any | None]]: URL de download do arquivo.
|
75
|
+
"""
|
76
|
+
from .authenticate import UdemyAuth
|
77
|
+
auth = UdemyAuth()
|
78
|
+
if not auth.verif_login():
|
79
|
+
raise LoginException("Sessão expirada!")
|
80
|
+
download_urls = []
|
81
|
+
for files in self.__data:
|
82
|
+
lecture_id = files.get('lecture_id', None)
|
83
|
+
asset_id = files.get('asset_id', None)
|
84
|
+
title = files.get("title", None)
|
85
|
+
lecture_title = files.get('lecture_title', None)
|
86
|
+
external_link = files.get('ExternalLink', None)
|
87
|
+
if external_link:
|
88
|
+
lnk = get_external_liks(course_id=self.__id_course, id_lecture=lecture_id, asset_id=asset_id)
|
89
|
+
dt_file = {'title-file': title,
|
90
|
+
'lecture_title': lecture_title,
|
91
|
+
'lecture_id': lecture_id,
|
92
|
+
'external_link': external_link,
|
93
|
+
'data-file': lnk.get('external_url', None)}
|
94
|
+
return dt_file
|
95
|
+
if asset_id and title and lecture_id and not external_link:
|
96
|
+
resp = requests.get(
|
97
|
+
f"https://www.udemy.com/api-2.0/users/me/subscribed-courses/{self.__id_course}/lectures/"
|
98
|
+
f"{lecture_id}/supplementary-assets/{asset_id}/?fields[asset]=download_urls",
|
99
|
+
headers=HEADERS_USER)
|
100
|
+
if resp.status_code == 200:
|
101
|
+
da = json.loads(resp.text)
|
102
|
+
# Para cada dict de um arquivo colocar seu título:
|
103
|
+
dt_file = {'title-file': title,
|
104
|
+
'lecture_title': lecture_title,
|
105
|
+
'lecture_id': lecture_id,
|
106
|
+
'external_link': external_link,
|
107
|
+
'data-file': da['download_urls']}
|
108
|
+
download_urls.append(dt_file)
|
109
|
+
return download_urls
|
110
|
+
|
111
|
+
|
112
|
+
class Lecture:
|
113
|
+
"""Cria objetos aula (lecture) do curso e extrai os dados."""
|
114
|
+
|
115
|
+
def __init__(self, data: dict, course_id: int, additional_files):
|
116
|
+
"""
|
117
|
+
Inicializa o objeto Lecture.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
data (dict): Um dicionário contendo os dados da aula.
|
121
|
+
course_id (int): O ID do curso.
|
122
|
+
additional_files: Arquivos adicionais relacionados à aula.
|
123
|
+
"""
|
124
|
+
self.__course_id = course_id
|
125
|
+
self.__data = data
|
126
|
+
self.__additional_files = additional_files
|
127
|
+
self.__asset = self.__data.get("asset", {})
|
128
|
+
|
129
|
+
@property
|
130
|
+
def get_lecture_id(self) -> int:
|
131
|
+
"""
|
132
|
+
Obtém o ID da aula.
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
int: O ID da aula.
|
136
|
+
"""
|
137
|
+
return self.__data.get('id', 0)
|
138
|
+
|
139
|
+
@property
|
140
|
+
def get_description(self) -> str:
|
141
|
+
"""
|
142
|
+
Obtém a descrição da aula.
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
str: A descrição da aula.
|
146
|
+
"""
|
147
|
+
return remove_tag(str(self.__data.get('description')))
|
148
|
+
|
149
|
+
@property
|
150
|
+
def is_free(self) -> bool:
|
151
|
+
"""
|
152
|
+
Verifica se a aula é gratuita (aulas gratuitas estão disponíveis na apresentação do curso).
|
153
|
+
|
154
|
+
Returns:
|
155
|
+
bool: True se a aula for gratuita, False caso contrário.
|
156
|
+
"""
|
157
|
+
return self.__data.get('is_free', False)
|
158
|
+
|
159
|
+
@property
|
160
|
+
def get_thumbnail(self) -> dict:
|
161
|
+
"""
|
162
|
+
Obtém informações da miniatura (thumbnail) do vídeo.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
dict: Um dicionário contendo as URLs das miniaturas.
|
166
|
+
"""
|
167
|
+
thumbnail_sprite = self.__asset.get('thumbnail_sprite', {})
|
168
|
+
return {
|
169
|
+
'thumbnail_vtt_url': thumbnail_sprite.get('vtt_url'),
|
170
|
+
'thumbnail_img_url': thumbnail_sprite.get('img_url')
|
171
|
+
}
|
172
|
+
|
173
|
+
@property
|
174
|
+
def get_asset_type(self) -> str:
|
175
|
+
"""
|
176
|
+
Obtém o tipo de asset (Video, Article, etc.).
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
str: O tipo de asset.
|
180
|
+
"""
|
181
|
+
return self.__asset.get('asset_type', 'Undefined')
|
182
|
+
|
183
|
+
@property
|
184
|
+
def get_media_sources(self) -> list:
|
185
|
+
"""
|
186
|
+
Obtém dados de streaming.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
list: Uma lista contendo as fontes de mídia.
|
190
|
+
"""
|
191
|
+
return self.__asset.get('media_sources')
|
192
|
+
|
193
|
+
@property
|
194
|
+
def get_captions(self) -> list:
|
195
|
+
"""
|
196
|
+
Obtém as legendas.
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
list: Uma lista contendo as legendas.
|
200
|
+
"""
|
201
|
+
return self.__asset.get('captions')
|
202
|
+
|
203
|
+
@property
|
204
|
+
def get_external_url(self) -> list:
|
205
|
+
"""
|
206
|
+
Obtém links externos se houver.
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
list: Uma lista contendo os links externos.
|
210
|
+
"""
|
211
|
+
return self.__asset.get('external_url')
|
212
|
+
|
213
|
+
@property
|
214
|
+
def get_media_license_token(self) -> str:
|
215
|
+
"""
|
216
|
+
Obtém o token de acesso à aula se houver.
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
str: O token de acesso à aula.
|
220
|
+
"""
|
221
|
+
return self.__asset.get('media_license_token')
|
222
|
+
|
223
|
+
def course_is_drmed(self) -> DRM:
|
224
|
+
"""
|
225
|
+
Verifica se a aula possui DRM. Se sim, retorna as keys da aula.
|
226
|
+
|
227
|
+
Returns:
|
228
|
+
DRM: O objeto DRM contendo as keys da aula ou None.
|
229
|
+
"""
|
230
|
+
try:
|
231
|
+
d = DRM(license_token=self.get_media_license_token,
|
232
|
+
get_media_sources=self.get_media_sources)
|
233
|
+
return d
|
234
|
+
except Exception as e:
|
235
|
+
DeprecationWarning(e)
|
236
|
+
|
237
|
+
@property
|
238
|
+
def get_download_urls(self) -> list:
|
239
|
+
"""
|
240
|
+
Obtém URLs de download se houver.
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
list: Uma lista contendo as URLs de download.
|
244
|
+
"""
|
245
|
+
return self.__asset.get('download_urls')
|
246
|
+
|
247
|
+
@property
|
248
|
+
def get_slide_urls(self) -> list:
|
249
|
+
"""
|
250
|
+
Obtém URLs de slides se houver.
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
list: Uma lista contendo as URLs de slides.
|
254
|
+
"""
|
255
|
+
return self.__asset.get('slide_urls')
|
256
|
+
|
257
|
+
@property
|
258
|
+
def get_slides(self) -> list:
|
259
|
+
"""
|
260
|
+
Obtém slides se houver.
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
list: Uma lista contendo os slides.
|
264
|
+
"""
|
265
|
+
return self.__asset.get('slides')
|
266
|
+
|
267
|
+
@property
|
268
|
+
def get_articles(self):
|
269
|
+
"""
|
270
|
+
Obtém os artigos relacionados à aula.
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
Os artigos relacionados à aula.
|
274
|
+
"""
|
275
|
+
d = assets_infor(course_id=self.__course_id, id_lecture=self.get_lecture_id, assets_id=self.__asset.get("id"))
|
276
|
+
return d
|
277
|
+
|
278
|
+
@property
|
279
|
+
def get_resources(self):
|
280
|
+
"""
|
281
|
+
Obtém os recursos adicionais relacionados à aula.
|
282
|
+
|
283
|
+
Returns:
|
284
|
+
Os recursos adicionais relacionados à aula.
|
285
|
+
"""
|
286
|
+
files_add = get_files_aule(lecture_id_filter=self.get_lecture_id, data=self.__additional_files)
|
287
|
+
f = Files(files=files_add, id_course=self.__course_id).get_download_url
|
288
|
+
return f
|
289
|
+
|
290
|
+
|
291
|
+
class Course:
|
292
|
+
"""Recebe um dicionário com os dados do curso."""
|
293
|
+
|
294
|
+
def __init__(self, results: dict, course_id: int):
|
295
|
+
"""
|
296
|
+
Inicializa o objeto Course.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
results (dict): Um dicionário contendo os dados do curso.
|
300
|
+
course_id (int): O ID do curso.
|
301
|
+
"""
|
302
|
+
self.__parser_chapers = parser_chapers(results=results)
|
303
|
+
self.__data = self.__parser_chapers
|
304
|
+
self.__course_id = course_id
|
305
|
+
self.__results = results
|
306
|
+
self.__additional_files_data = get_add_files(course_id)
|
307
|
+
self.__information = self.__load_infor_course()
|
308
|
+
|
309
|
+
def __load_infor_course(self) -> dict:
|
310
|
+
"""
|
311
|
+
Obtém as informações do curso.
|
312
|
+
|
313
|
+
Returns:
|
314
|
+
dict: Um dicionário contendo as informações do curso.
|
315
|
+
"""
|
316
|
+
data = get_course_infor(self.__course_id)
|
317
|
+
return data
|
318
|
+
|
319
|
+
@property
|
320
|
+
def title_course(self) -> str:
|
321
|
+
"""
|
322
|
+
Obtém o título do curso.
|
323
|
+
|
324
|
+
Returns:
|
325
|
+
str: O título do curso.
|
326
|
+
"""
|
327
|
+
return self.__information.get('title')
|
328
|
+
|
329
|
+
@property
|
330
|
+
def instructors(self) -> dict:
|
331
|
+
"""
|
332
|
+
Obtém informações dos instrutores.
|
333
|
+
|
334
|
+
Returns:
|
335
|
+
dict: Um dicionário contendo as informações dos instrutores.
|
336
|
+
"""
|
337
|
+
return self.__information.get("visible_instructors")
|
338
|
+
|
339
|
+
@property
|
340
|
+
def locale(self):
|
341
|
+
"""
|
342
|
+
Obtém informações de localidade do curso.
|
343
|
+
|
344
|
+
Returns:
|
345
|
+
str: As informações de localidade do curso.
|
346
|
+
"""
|
347
|
+
return self.__information.get('locale')
|
348
|
+
|
349
|
+
@property
|
350
|
+
def primary_category(self):
|
351
|
+
"""
|
352
|
+
Obtém a categoria primária.
|
353
|
+
|
354
|
+
Returns:
|
355
|
+
str: A categoria primária.
|
356
|
+
"""
|
357
|
+
return self.__information.get('primary_category')
|
358
|
+
|
359
|
+
@property
|
360
|
+
def primary_subcategory(self):
|
361
|
+
"""
|
362
|
+
Obtém a subcategoria primária.
|
363
|
+
|
364
|
+
Returns:
|
365
|
+
str: A subcategoria primária.
|
366
|
+
"""
|
367
|
+
return self.__information.get('primary_subcategory')
|
368
|
+
|
369
|
+
@property
|
370
|
+
def count_lectures(self) -> int:
|
371
|
+
"""
|
372
|
+
Obtém o número total de lectures no curso.
|
373
|
+
|
374
|
+
Returns:
|
375
|
+
int: O número total de lectures no curso.
|
376
|
+
"""
|
377
|
+
total_lectures = 0
|
378
|
+
for chapter in self.__data.values():
|
379
|
+
total_lectures += len(chapter.get('videos_in_chapter', []))
|
380
|
+
return total_lectures
|
381
|
+
|
382
|
+
@property
|
383
|
+
def count_chapters(self) -> int:
|
384
|
+
"""
|
385
|
+
Obtém o número total de chapters (sections) no curso.
|
386
|
+
|
387
|
+
Returns:
|
388
|
+
int: O número total de chapters (sections) no curso.
|
389
|
+
"""
|
390
|
+
return len(self.__data)
|
391
|
+
|
392
|
+
@property
|
393
|
+
def title_videos(self) -> list:
|
394
|
+
"""
|
395
|
+
Obtém uma lista com todos os títulos de vídeos no curso.
|
396
|
+
|
397
|
+
Returns:
|
398
|
+
list: Uma lista contendo os títulos de vídeos no curso.
|
399
|
+
"""
|
400
|
+
videos = []
|
401
|
+
for chapter in self.__data.values():
|
402
|
+
for video in chapter.get('videos_in_chapter', []):
|
403
|
+
title = video['video_title']
|
404
|
+
if title != "Files":
|
405
|
+
videos.append(title)
|
406
|
+
return videos
|
407
|
+
|
408
|
+
@property
|
409
|
+
def get_lectures(self) -> list:
|
410
|
+
"""
|
411
|
+
Obtém uma lista com todas as aulas.
|
412
|
+
|
413
|
+
Returns:
|
414
|
+
list: Uma lista contendo todas as aulas.
|
415
|
+
"""
|
416
|
+
videos = []
|
417
|
+
section_order = 1 # Iniciar a numeração das seções (capítulos)
|
418
|
+
|
419
|
+
for chapter in self.__data.values():
|
420
|
+
for index, video in enumerate(chapter.get('videos_in_chapter', [])):
|
421
|
+
section = f"{chapter.get('title_chapter')}" # Adicionar numeração da seção
|
422
|
+
title = video.get('video_title')
|
423
|
+
id_lecture = video.get('lecture_id')
|
424
|
+
id_asset = video.get('asset_id')
|
425
|
+
dt = {
|
426
|
+
'section': section,
|
427
|
+
'title': title,
|
428
|
+
'lecture_id': id_lecture,
|
429
|
+
'asset_id': id_asset,
|
430
|
+
'section_order': section_order
|
431
|
+
}
|
432
|
+
videos.append(dt)
|
433
|
+
section_order += 1 # Incrementar o número da seção após processar os vídeos do capítulo
|
434
|
+
return videos
|
435
|
+
|
436
|
+
def get_details_lecture(self, lecture_id: int) -> Lecture:
|
437
|
+
"""
|
438
|
+
Obtém detalhes de uma aula específica.
|
439
|
+
|
440
|
+
Args:
|
441
|
+
lecture_id (int): O ID da aula.
|
442
|
+
|
443
|
+
Returns:
|
444
|
+
Lecture: Um objeto Lecture contendo os detalhes da aula.
|
445
|
+
"""
|
446
|
+
links = get_links(course_id=self.__course_id, id_lecture=lecture_id)
|
447
|
+
additional_files = self.__load_assets()
|
448
|
+
lecture = Lecture(data=links, course_id=self.__course_id, additional_files=additional_files)
|
449
|
+
return lecture
|
450
|
+
|
451
|
+
@property
|
452
|
+
def get_additional_files(self) -> list:
|
453
|
+
"""
|
454
|
+
Retorna a lista de arquivos adicionais de um curso.
|
455
|
+
|
456
|
+
Returns:
|
457
|
+
list: Uma lista contendo os arquivos adicionais de um curso.
|
458
|
+
"""
|
459
|
+
supplementary_assets = []
|
460
|
+
for item in self.__additional_files_data.get('results', []):
|
461
|
+
if item.get('_class') == 'lecture':
|
462
|
+
id_l = item.get('id', {})
|
463
|
+
title = item.get('title', {})
|
464
|
+
assets = item.get('supplementary_assets', [])
|
465
|
+
for asset in assets:
|
466
|
+
supplementary_assets.append({
|
467
|
+
'lecture_id': id_l,
|
468
|
+
'lecture_title': title,
|
469
|
+
'asset': asset
|
470
|
+
})
|
471
|
+
files = extract_files(supplementary_assets)
|
472
|
+
files_objt = Files(files=files, id_course=self.__course_id).get_download_url
|
473
|
+
return files_objt
|
474
|
+
|
475
|
+
def __load_assets(self):
|
476
|
+
"""
|
477
|
+
Retorna a lista de arquivos adicionais de um curso.
|
478
|
+
|
479
|
+
Returns:
|
480
|
+
list: Uma lista contendo os arquivos adicionais de um curso.
|
481
|
+
"""
|
482
|
+
supplementary_assets = []
|
483
|
+
for item in self.__additional_files_data.get('results', []):
|
484
|
+
if item.get('_class') == 'lecture':
|
485
|
+
id_l = item.get('id')
|
486
|
+
title = item.get('title')
|
487
|
+
assets = item.get('supplementary_assets', [])
|
488
|
+
for asset in assets:
|
489
|
+
supplementary_assets.append({
|
490
|
+
'lecture_id': id_l,
|
491
|
+
'lecture_title': title,
|
492
|
+
'asset': asset
|
493
|
+
})
|
494
|
+
files = extract_files(supplementary_assets)
|
495
|
+
return files
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class UdemyUserApiExceptions(Exception):
|
2
|
+
def __init__(self, message="Udemy_UserApi Generic Error!"):
|
3
|
+
self.message = message
|
4
|
+
super().__init__(self.message)
|
5
|
+
|
6
|
+
|
7
|
+
class Upstreamconnecterror(Exception):
|
8
|
+
def __init__(self, message="Falha na conexão com o servidor!"):
|
9
|
+
self.message = message
|
10
|
+
super().__init__(self.message)
|
11
|
+
|
12
|
+
|
13
|
+
class UnhandledExceptions(Exception):
|
14
|
+
def __init__(self, message="Error Unhandled!"):
|
15
|
+
self.message = message
|
16
|
+
super().__init__(self.message)
|
17
|
+
|
18
|
+
|
19
|
+
class LoginException(Exception):
|
20
|
+
def __init__(self, message="Error Login!"):
|
21
|
+
self.message = message
|
22
|
+
super().__init__(self.message)
|
Binary file
|