udemy-userAPI 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)
@@ -0,0 +1,3 @@
1
+ # Biblioteca para análise de arquivos MPD
2
+ from .mpd_parser import MPDParser
3
+ __version__ = '0.1.0'
Binary file