udemy-userAPI 0.3.5__tar.gz → 0.3.7__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {udemy_userapi-0.3.5/udemy_userAPI.egg-info → udemy_userapi-0.3.7}/PKG-INFO +13 -3
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/README.md +1 -1
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/README_PYPI.md +1 -1
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/__version__.py +1 -1
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/api.py +139 -39
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/authenticate.py +1 -1
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/bultins.py +243 -56
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/sections.py +2 -1
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/udemy.py +13 -4
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7/udemy_userAPI.egg-info}/PKG-INFO +13 -3
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/LICENSE +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/MANIFEST.in +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/setup.cfg +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/setup.py +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/.cache/.udemy_userAPI +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/__init__.py +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/exeptions.py +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/mpd_analyzer/__init__.py +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/mpd_analyzer/bin.wvd +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI/mpd_analyzer/mpd_parser.py +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI.egg-info/SOURCES.txt +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI.egg-info/dependency_links.txt +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI.egg-info/not-zip-safe +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI.egg-info/requires.txt +0 -0
- {udemy_userapi-0.3.5 → udemy_userapi-0.3.7}/udemy_userAPI.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: udemy_userAPI
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.7
|
4
4
|
Summary: Obtenha detalhes de cursos que o usuário esteja inscrito da plataforma Udemy,usando o EndPoint de usuário o mesmo que o navegador utiliza para acessar e redenrizar os cursos.
|
5
5
|
Author: PauloCesar-dev404
|
6
6
|
Author-email: paulocesar0073dev404@gmail.com
|
@@ -15,11 +15,21 @@ License-File: LICENSE
|
|
15
15
|
Requires-Dist: requests
|
16
16
|
Requires-Dist: cloudscraper
|
17
17
|
Requires-Dist: pywidevine
|
18
|
+
Dynamic: author
|
19
|
+
Dynamic: author-email
|
20
|
+
Dynamic: description
|
21
|
+
Dynamic: description-content-type
|
22
|
+
Dynamic: keywords
|
23
|
+
Dynamic: license
|
24
|
+
Dynamic: platform
|
25
|
+
Dynamic: project-url
|
26
|
+
Dynamic: requires-dist
|
27
|
+
Dynamic: summary
|
18
28
|
|
19
29
|
# udemy-userAPI
|
20
30
|
|
21
31
|
|
22
|
-

|
23
33
|

|
24
34
|
[](https://paulocesar-dev404.github.io/me-apoiando-online/)
|
25
35
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
|
5
5
|
|
6
|
-

|
7
7
|

|
8
8
|
[](https://paulocesar-dev404.github.io/me-apoiando-online/)
|
9
9
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# udemy-userAPI
|
2
2
|
|
3
3
|
|
4
|
-

|
5
5
|

|
6
6
|
[](https://paulocesar-dev404.github.io/me-apoiando-online/)
|
7
7
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
|
@@ -134,7 +134,8 @@ def extract(pssh, license_token):
|
|
134
134
|
from .authenticate import UdemyAuth
|
135
135
|
auth = UdemyAuth()
|
136
136
|
if not auth.verif_login():
|
137
|
-
raise LoginException(
|
137
|
+
raise LoginException(
|
138
|
+
"Sessão expirada!")
|
138
139
|
license_url = (f"https://www.udemy.com/api-2.0/media-license-server/validate-auth-token?drm_type=widevine"
|
139
140
|
f"&auth_token={license_token}")
|
140
141
|
session_id = cdm.open()
|
@@ -184,43 +185,58 @@ def get_mpd_file(mpd_url):
|
|
184
185
|
raise UnhandledExceptions(f"Errro Ao Obter Mídias:{e}")
|
185
186
|
|
186
187
|
|
187
|
-
def
|
188
|
-
"""
|
189
|
-
:param results:
|
190
|
-
:return:
|
191
|
-
"""
|
192
|
-
if not results:
|
193
|
-
raise UdemyUserApiExceptions("Não foi possível obter detalhes do curso!")
|
194
|
-
results = results.get('results', None)
|
188
|
+
def parser_chapters(results) -> list[dict]:
|
195
189
|
if not results:
|
196
190
|
raise UdemyUserApiExceptions("Não foi possível obter detalhes do curso!")
|
197
|
-
chapters_dict = {} # Dicionário para armazenar os capítulos e seus vídeos correspondentes
|
198
191
|
|
199
|
-
|
200
|
-
|
192
|
+
results = results.get('results', [])
|
193
|
+
chapters_dicts = [] # Lista que armazena todos os capítulos
|
194
|
+
current_chapter = None # Capítulo atual
|
195
|
+
|
201
196
|
for dictionary in results:
|
202
197
|
_class = dictionary.get('_class')
|
198
|
+
chapter_index = dictionary.get('object_index')
|
203
199
|
|
200
|
+
# Quando encontrar um novo capítulo
|
204
201
|
if _class == 'chapter':
|
205
|
-
|
202
|
+
if current_chapter: # Se já há um capítulo atual, adicionamos
|
203
|
+
chapters_dicts.append(current_chapter)
|
204
|
+
|
205
|
+
# Inicia um novo capítulo
|
206
206
|
current_chapter = {
|
207
|
-
'
|
208
|
-
'
|
207
|
+
'title': dictionary.get('title'),
|
208
|
+
'chapter_index': chapter_index,
|
209
|
+
'lectures': [] # Lista para armazenar aulas e quizzes
|
209
210
|
}
|
210
|
-
|
211
|
-
|
211
|
+
|
212
|
+
# Se for uma aula, adiciona ao capítulo atual
|
213
|
+
elif _class == 'lecture' and current_chapter:
|
212
214
|
asset = dictionary.get('asset')
|
213
215
|
if asset:
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
'
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
216
|
+
lecture_data = {
|
217
|
+
'asset_type': asset.get('asset_type', ''),
|
218
|
+
'title': dictionary.get('title', 'Files'),
|
219
|
+
'lecture_id': dictionary.get('id', ''),
|
220
|
+
'asset_id': asset.get('id', '')
|
221
|
+
}
|
222
|
+
current_chapter['lectures'].append(lecture_data)
|
223
|
+
|
224
|
+
# Se for um quiz, também adiciona ao capítulo atual
|
225
|
+
elif _class == 'quiz' and current_chapter:
|
226
|
+
quiz_data = {
|
227
|
+
'asset_type': 'quiz',
|
228
|
+
'title': dictionary.get('title', 'Quiz'),
|
229
|
+
'lecture_id': dictionary.get('id', ''),
|
230
|
+
'type': dictionary.get('type', ''),
|
231
|
+
'asset_id': ''
|
232
|
+
}
|
233
|
+
current_chapter['lectures'].append(quiz_data)
|
234
|
+
|
235
|
+
# Adiciona o último capítulo processado
|
236
|
+
if current_chapter:
|
237
|
+
chapters_dicts.append(current_chapter)
|
238
|
+
|
239
|
+
return chapters_dicts
|
224
240
|
|
225
241
|
|
226
242
|
def get_add_files(course_id: int):
|
@@ -304,11 +320,11 @@ def get_links(course_id: int, id_lecture: int):
|
|
304
320
|
erro ao obter dados das aulas.
|
305
321
|
"""
|
306
322
|
get = (f"https://www.udemy.com/api-2.0/users/me/subscribed-courses/{course_id}/lectures/{id_lecture}/?"
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
323
|
+
f"fields[lecture]"
|
324
|
+
f"=asset,description,download_url,is_free,last_watched_second&fields[asset]=asset_type,length,"
|
325
|
+
f"media_license_token,course_is_drmed,media_sources,captions,thumbnail_sprite,slides,slide_urls,"
|
326
|
+
f"download_urls,"
|
327
|
+
f"external_url&q=0.3108014137011559/?fields[asset]=download_urls")
|
312
328
|
from .authenticate import UdemyAuth
|
313
329
|
auth = UdemyAuth()
|
314
330
|
if not auth.verif_login():
|
@@ -322,18 +338,96 @@ def get_links(course_id: int, id_lecture: int):
|
|
322
338
|
a = json.loads(response.text)
|
323
339
|
return a
|
324
340
|
else:
|
325
|
-
raise UnhandledExceptions(
|
341
|
+
raise UnhandledExceptions(
|
342
|
+
f"Erro ao obter dados da aula! Código de status: {response.status_code}")
|
326
343
|
|
327
344
|
except requests.ConnectionError as e:
|
328
|
-
raise UdemyUserApiExceptions(
|
345
|
+
raise UdemyUserApiExceptions(
|
346
|
+
f"Erro de conexão: {e}")
|
329
347
|
except requests.Timeout as e:
|
330
|
-
raise UdemyUserApiExceptions(
|
348
|
+
raise UdemyUserApiExceptions(
|
349
|
+
f"Tempo de requisição excedido: {e}")
|
331
350
|
except requests.TooManyRedirects as e:
|
332
|
-
raise UdemyUserApiExceptions(
|
351
|
+
raise UdemyUserApiExceptions(
|
352
|
+
f"Limite de redirecionamentos excedido: {e}")
|
333
353
|
except requests.HTTPError as e:
|
334
|
-
raise UdemyUserApiExceptions(
|
354
|
+
raise UdemyUserApiExceptions(
|
355
|
+
f"Erro HTTP: {e}")
|
335
356
|
except Exception as e:
|
336
|
-
raise UnhandledExceptions(
|
357
|
+
raise UnhandledExceptions(
|
358
|
+
f"Erro ao obter mídias: {e}")
|
359
|
+
|
360
|
+
def get_assessments(course_id: int,lecture_id:int):
|
361
|
+
get = (f'https://www.udemy.com/api-2.0/users/me/subscribed-courses/{course_id}/quizzes/{lecture_id}/?draft='
|
362
|
+
f'false&fields[quiz]=id,type,title,description,object_index,num_assessments,version,duration,'
|
363
|
+
f'is_draft,pass_percent,changelog')
|
364
|
+
from .authenticate import UdemyAuth
|
365
|
+
auth = UdemyAuth()
|
366
|
+
if not auth.verif_login():
|
367
|
+
raise LoginException("Sessão expirada!")
|
368
|
+
try:
|
369
|
+
# Faz a solicitação GET com os cabeçalhos
|
370
|
+
response = requests.get(get, headers=HEADERS_USER)
|
371
|
+
data = []
|
372
|
+
# Exibe o código de status
|
373
|
+
if response.status_code == 200:
|
374
|
+
a = json.loads(response.text)
|
375
|
+
return a
|
376
|
+
else:
|
377
|
+
raise ConnectionError(
|
378
|
+
f"Erro ao obter dados da aula! Código de status: {response.status_code}\n"
|
379
|
+
f"{response.text}"
|
380
|
+
)
|
381
|
+
|
382
|
+
except requests.ConnectionError as e:
|
383
|
+
raise UdemyUserApiExceptions(
|
384
|
+
f"Erro de conexão: {e}")
|
385
|
+
except requests.Timeout as e:
|
386
|
+
raise UdemyUserApiExceptions(
|
387
|
+
f"Tempo de requisição excedido: {e}")
|
388
|
+
except requests.TooManyRedirects as e:
|
389
|
+
raise UdemyUserApiExceptions(
|
390
|
+
f"Limite de redirecionamentos excedido: {e}")
|
391
|
+
except requests.HTTPError as e:
|
392
|
+
raise UdemyUserApiExceptions(
|
393
|
+
f"Erro HTTP: {e}")
|
394
|
+
|
395
|
+
|
396
|
+
def get_quizzes(lecture_id:int):
|
397
|
+
get = (f'https://www.udemy.com/api-2.0/quizzes/{lecture_id}/assessments/?version=1&page_size=1000&fields[assessment]'
|
398
|
+
f'=id,assessment_type,prompt,correct_response,section,question_plain,related_lectures&use_remote_version=true'
|
399
|
+
)
|
400
|
+
from .authenticate import UdemyAuth
|
401
|
+
auth = UdemyAuth()
|
402
|
+
if not auth.verif_login():
|
403
|
+
raise LoginException("Sessão expirada!")
|
404
|
+
try:
|
405
|
+
# Faz a solicitação GET com os cabeçalhos
|
406
|
+
response = requests.get(get, headers=HEADERS_USER)
|
407
|
+
data = []
|
408
|
+
# Exibe o código de status
|
409
|
+
if response.status_code == 200:
|
410
|
+
a = json.loads(response.text)
|
411
|
+
return a
|
412
|
+
else:
|
413
|
+
raise UnhandledExceptions(
|
414
|
+
f"Erro ao obter dados da aula! Código de status: {response.status_code}")
|
415
|
+
|
416
|
+
except requests.ConnectionError as e:
|
417
|
+
raise UdemyUserApiExceptions(
|
418
|
+
f"Erro de conexão: {e}")
|
419
|
+
except requests.Timeout as e:
|
420
|
+
raise UdemyUserApiExceptions(
|
421
|
+
f"Tempo de requisição excedido: {e}")
|
422
|
+
except requests.TooManyRedirects as e:
|
423
|
+
raise UdemyUserApiExceptions(
|
424
|
+
f"Limite de redirecionamentos excedido: {e}")
|
425
|
+
except requests.HTTPError as e:
|
426
|
+
raise UdemyUserApiExceptions(
|
427
|
+
f"Erro HTTP: {e}")
|
428
|
+
except Exception as e:
|
429
|
+
raise UnhandledExceptions(
|
430
|
+
f"Erro ao obter mídias: {e}")
|
337
431
|
|
338
432
|
|
339
433
|
def remove_tag(d: str):
|
@@ -607,6 +701,7 @@ def assets_infor(course_id: int, id_lecture: int, assets_id: int):
|
|
607
701
|
f"{r.text}")
|
608
702
|
|
609
703
|
|
704
|
+
|
610
705
|
def save_html(body, title_lecture):
|
611
706
|
html_content = f"""<!DOCTYPE html>
|
612
707
|
<html lang="en">
|
@@ -621,7 +716,6 @@ def save_html(body, title_lecture):
|
|
621
716
|
|
622
717
|
return html_content
|
623
718
|
|
624
|
-
|
625
719
|
def J(e, t):
|
626
720
|
"""
|
627
721
|
Gera um identificador único baseado na data atual e nas funções X e ee.
|
@@ -689,3 +783,9 @@ def te(e, t):
|
|
689
783
|
s = t[:r]
|
690
784
|
o = ''.join(format(byte, '08b') for byte in s)
|
691
785
|
return o.startswith('0' * e)
|
786
|
+
def is_lecture_in_course(lectures,lecture_id) -> bool:
|
787
|
+
# Verifica se o lecture_id está presente na lista de aulas
|
788
|
+
for lecture in lectures:
|
789
|
+
if lecture.get('lecture_id') == lecture_id:
|
790
|
+
return True
|
791
|
+
return False
|
@@ -105,7 +105,7 @@ class UdemyAuth:
|
|
105
105
|
except requests.HTTPError as e:
|
106
106
|
raise UdemyUserApiExceptions(f"Erro HTTP: {e}")
|
107
107
|
except Exception as e:
|
108
|
-
raise UnhandledExceptions(f"
|
108
|
+
raise UnhandledExceptions(f"{e}")
|
109
109
|
else:
|
110
110
|
return False
|
111
111
|
|
@@ -1,11 +1,8 @@
|
|
1
|
-
import json
|
2
1
|
from typing import Any
|
3
|
-
import
|
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
|
2
|
+
from .api import *
|
8
3
|
from .exeptions import LoginException
|
4
|
+
from .mpd_analyzer import MPDParser
|
5
|
+
from .sections import get_course_infor
|
9
6
|
|
10
7
|
|
11
8
|
class DRM:
|
@@ -108,6 +105,171 @@ class Files:
|
|
108
105
|
download_urls.append(dt_file)
|
109
106
|
return download_urls
|
110
107
|
|
108
|
+
class Quiz:
|
109
|
+
"""Representa um quiz.
|
110
|
+
"""
|
111
|
+
|
112
|
+
def __init__(self, quiz_data: dict):
|
113
|
+
"""
|
114
|
+
Inicializa uma instância de Quiz.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
quiz_data (dict): Dados do quiz.
|
118
|
+
"""
|
119
|
+
self._data = quiz_data
|
120
|
+
|
121
|
+
@property
|
122
|
+
def id(self) -> int:
|
123
|
+
"""Retorna o ID do quiz."""
|
124
|
+
return self._data.get('id', 0)
|
125
|
+
|
126
|
+
@property
|
127
|
+
def title(self) -> str:
|
128
|
+
"""Retorna o título do quiz."""
|
129
|
+
return self._data.get('title', '')
|
130
|
+
|
131
|
+
@property
|
132
|
+
def type_quiz(self) -> str:
|
133
|
+
"""Retorna o tipo de quiz (exame ou prática)."""
|
134
|
+
return self._data.get('type', '')
|
135
|
+
|
136
|
+
@property
|
137
|
+
def description(self) -> str:
|
138
|
+
"""Retorna a descrição do quiz."""
|
139
|
+
return remove_tag(self._data.get('description', ''))
|
140
|
+
|
141
|
+
@property
|
142
|
+
def duration(self) -> int:
|
143
|
+
"""Retorna a duração do quiz em minutos, se aplicável."""
|
144
|
+
duration: int = self._data.get('duration', 1)
|
145
|
+
if duration > 1:
|
146
|
+
return int(duration / 60)
|
147
|
+
else:
|
148
|
+
return 0
|
149
|
+
|
150
|
+
@property
|
151
|
+
def pass_percent(self) -> int:
|
152
|
+
"""Retorna a porcentagem necessária para passar."""
|
153
|
+
return self._data.get('pass_percent', 0)
|
154
|
+
|
155
|
+
@property
|
156
|
+
def num_assessments(self) -> int:
|
157
|
+
"""Retorna o número de perguntas do quiz."""
|
158
|
+
return self._data.get('num_assessments', 0)
|
159
|
+
|
160
|
+
def content(self) -> dict:
|
161
|
+
"""Obtém o conteúdo do quiz."""
|
162
|
+
htmls = get_quizzes(lecture_id=self.id)
|
163
|
+
return htmls
|
164
|
+
|
165
|
+
|
166
|
+
class Caption:
|
167
|
+
"""Representa uma legenda."""
|
168
|
+
|
169
|
+
def __init__(self, caption: dict):
|
170
|
+
"""
|
171
|
+
Inicializa uma instância de Caption.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
caption (dict): Dados da legenda.
|
175
|
+
"""
|
176
|
+
self._caption = caption
|
177
|
+
|
178
|
+
@property
|
179
|
+
def locale(self) -> str:
|
180
|
+
"""Retorna o idioma."""
|
181
|
+
return self._caption.get('video_label', '')
|
182
|
+
|
183
|
+
@property
|
184
|
+
def status(self) -> str:
|
185
|
+
"""Retorna o status da legenda 1 ou 0"""
|
186
|
+
return self._caption.get('status')
|
187
|
+
|
188
|
+
@property
|
189
|
+
def title(self) -> str:
|
190
|
+
"""Retorna o título da legenda."""
|
191
|
+
return self._caption.get('title', '')
|
192
|
+
|
193
|
+
@property
|
194
|
+
def created(self) -> str:
|
195
|
+
"""Retorna a data de criação da legenda."""
|
196
|
+
return self._caption.get('created', '')
|
197
|
+
|
198
|
+
@property
|
199
|
+
def id(self) -> int:
|
200
|
+
"""Retorna o ID da legenda."""
|
201
|
+
return self._caption.get('id', 0)
|
202
|
+
|
203
|
+
@property
|
204
|
+
def url(self) -> str:
|
205
|
+
"""Retorna a URL da legenda."""
|
206
|
+
return self._caption.get('url', '')
|
207
|
+
|
208
|
+
@property
|
209
|
+
def content(self) -> str:
|
210
|
+
"""Obtém o conteúdo da legenda."""
|
211
|
+
if self.url:
|
212
|
+
r = requests.get(headers=HEADERS_USER, url=self.url)
|
213
|
+
if r.status_code == 200:
|
214
|
+
return r.text
|
215
|
+
else:
|
216
|
+
raise ConnectionError(
|
217
|
+
f'status_code: {r.status_code}, Não foi possível obter o conteúdo da legenda!'
|
218
|
+
)
|
219
|
+
else:
|
220
|
+
raise FileNotFoundError(
|
221
|
+
'Não foi possível obter a URL da legenda!'
|
222
|
+
)
|
223
|
+
|
224
|
+
class Captions:
|
225
|
+
"""Gerencia as legendas de um vídeo."""
|
226
|
+
|
227
|
+
def __init__(self, caption_data: list):
|
228
|
+
"""
|
229
|
+
Inicializa uma instância de Captions.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
caption_data (list): Dados das legendas.
|
233
|
+
"""
|
234
|
+
self._caption_data = caption_data
|
235
|
+
|
236
|
+
def languages(self) -> list[dict]:
|
237
|
+
"""Retorna a lista de idiomas disponíveis na aula."""
|
238
|
+
langs = []
|
239
|
+
for caption in self._caption_data:
|
240
|
+
locale_id = caption.get('locale_id', '')
|
241
|
+
video_label = caption.get('video_label','')
|
242
|
+
if locale_id:
|
243
|
+
langs.append({'locale_id': locale_id,'locale':video_label})
|
244
|
+
return langs
|
245
|
+
|
246
|
+
def get_lang(self, locale_id: str) -> Caption:
|
247
|
+
"""
|
248
|
+
Obtém a legenda para o idioma especificado.
|
249
|
+
|
250
|
+
|
251
|
+
Args:
|
252
|
+
locale_id (str): ID do idioma,pode ser obtido no método -> 'languages'
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
Caption: Objeto Caption.
|
256
|
+
|
257
|
+
Raises:
|
258
|
+
FileNotFoundError: Se o idioma não estiver disponível na aula.
|
259
|
+
"""
|
260
|
+
is_t = False
|
261
|
+
cpt = {}
|
262
|
+
for caption in self._caption_data:
|
263
|
+
if locale_id == caption.get('locale_id'):
|
264
|
+
is_t = True
|
265
|
+
cpt = caption
|
266
|
+
if not is_t:
|
267
|
+
raise FileNotFoundError(
|
268
|
+
'Esse idioma não está disponível nessa aula!'
|
269
|
+
)
|
270
|
+
c = Caption(caption=cpt)
|
271
|
+
return c
|
272
|
+
|
111
273
|
|
112
274
|
class Lecture:
|
113
275
|
"""Cria objetos aula (lecture) do curso e extrai os dados."""
|
@@ -166,8 +328,8 @@ class Lecture:
|
|
166
328
|
"""
|
167
329
|
thumbnail_sprite = self.__asset.get('thumbnail_sprite', {})
|
168
330
|
return {
|
169
|
-
'thumbnail_vtt_url': thumbnail_sprite.get('vtt_url'),
|
170
|
-
'thumbnail_img_url': thumbnail_sprite.get('img_url')
|
331
|
+
'thumbnail_vtt_url': thumbnail_sprite.get('vtt_url',[]),
|
332
|
+
'thumbnail_img_url': thumbnail_sprite.get('img_url',[])
|
171
333
|
}
|
172
334
|
|
173
335
|
@property
|
@@ -178,7 +340,7 @@ class Lecture:
|
|
178
340
|
Returns:
|
179
341
|
str: O tipo de asset.
|
180
342
|
"""
|
181
|
-
return self.__asset.get('asset_type', '
|
343
|
+
return self.__asset.get('asset_type', '') or self.__data.get('_class','').replace('quiz','Quiz')
|
182
344
|
|
183
345
|
@property
|
184
346
|
def get_media_sources(self) -> list:
|
@@ -188,18 +350,23 @@ class Lecture:
|
|
188
350
|
Returns:
|
189
351
|
list: Uma lista contendo as fontes de mídia.
|
190
352
|
"""
|
191
|
-
return self.__asset.get('media_sources')
|
353
|
+
return self.__asset.get('media_sources',[])
|
192
354
|
|
193
355
|
@property
|
194
|
-
def get_captions(self) ->
|
356
|
+
def get_captions(self) -> Captions:
|
195
357
|
"""
|
196
358
|
Obtém as legendas.
|
197
359
|
|
198
360
|
Returns:
|
199
|
-
|
200
|
-
"""
|
201
|
-
|
202
|
-
|
361
|
+
Captions: Objeto para gerenciar as legendas.
|
362
|
+
"""
|
363
|
+
if self.__asset.get('captions',[]):
|
364
|
+
c = Captions(caption_data=self.__asset.get('captions',[]))
|
365
|
+
return c
|
366
|
+
else:
|
367
|
+
raise FileNotFoundError(
|
368
|
+
'Não foi encontrada legendas nessa aula!'
|
369
|
+
)
|
203
370
|
@property
|
204
371
|
def get_external_url(self) -> list:
|
205
372
|
"""
|
@@ -208,7 +375,7 @@ class Lecture:
|
|
208
375
|
Returns:
|
209
376
|
list: Uma lista contendo os links externos.
|
210
377
|
"""
|
211
|
-
return self.__asset.get('external_url')
|
378
|
+
return self.__asset.get('external_url',[])
|
212
379
|
|
213
380
|
@property
|
214
381
|
def get_media_license_token(self) -> str:
|
@@ -218,7 +385,7 @@ class Lecture:
|
|
218
385
|
Returns:
|
219
386
|
str: O token de acesso à aula.
|
220
387
|
"""
|
221
|
-
return self.__asset.get('media_license_token')
|
388
|
+
return self.__asset.get('media_license_token','')
|
222
389
|
|
223
390
|
def course_is_drmed(self) -> DRM:
|
224
391
|
"""
|
@@ -227,12 +394,19 @@ class Lecture:
|
|
227
394
|
Returns:
|
228
395
|
DRM: O objeto DRM contendo as keys da aula ou None.
|
229
396
|
"""
|
230
|
-
|
231
|
-
d = DRM(license_token=self.get_media_license_token,
|
397
|
+
d = DRM(license_token=self.get_media_license_token,
|
232
398
|
get_media_sources=self.get_media_sources)
|
233
|
-
|
234
|
-
|
235
|
-
|
399
|
+
return d
|
400
|
+
|
401
|
+
def quiz_object(self) ->Quiz:
|
402
|
+
"""se for um quiz ele retorna um objeto Quiz"""
|
403
|
+
if self.get_asset_type.lower() == 'quiz':
|
404
|
+
q =Quiz(get_assessments(lecture_id=self.get_lecture_id,course_id=self.__course_id))
|
405
|
+
return q
|
406
|
+
else:
|
407
|
+
raise UserWarning(
|
408
|
+
'Atenção essa aula não é um Quiz!'
|
409
|
+
)
|
236
410
|
|
237
411
|
@property
|
238
412
|
def get_download_urls(self) -> list:
|
@@ -242,7 +416,7 @@ class Lecture:
|
|
242
416
|
Returns:
|
243
417
|
list: Uma lista contendo as URLs de download.
|
244
418
|
"""
|
245
|
-
return self.__asset.get('download_urls')
|
419
|
+
return self.__asset.get('download_urls',[])
|
246
420
|
|
247
421
|
@property
|
248
422
|
def get_slide_urls(self) -> list:
|
@@ -252,7 +426,7 @@ class Lecture:
|
|
252
426
|
Returns:
|
253
427
|
list: Uma lista contendo as URLs de slides.
|
254
428
|
"""
|
255
|
-
return self.__asset.get('slide_urls')
|
429
|
+
return self.__asset.get('slide_urls',[])
|
256
430
|
|
257
431
|
@property
|
258
432
|
def get_slides(self) -> list:
|
@@ -262,7 +436,7 @@ class Lecture:
|
|
262
436
|
Returns:
|
263
437
|
list: Uma lista contendo os slides.
|
264
438
|
"""
|
265
|
-
return self.__asset.get('slides')
|
439
|
+
return self.__asset.get('slides',[])
|
266
440
|
|
267
441
|
@property
|
268
442
|
def get_articles(self):
|
@@ -272,8 +446,11 @@ class Lecture:
|
|
272
446
|
Returns:
|
273
447
|
Os artigos relacionados à aula.
|
274
448
|
"""
|
275
|
-
|
276
|
-
|
449
|
+
if self.__asset:
|
450
|
+
d = assets_infor(course_id=self.__course_id, id_lecture=self.get_lecture_id, assets_id=self.__asset.get("id"))
|
451
|
+
return d
|
452
|
+
else:
|
453
|
+
return []
|
277
454
|
|
278
455
|
@property
|
279
456
|
def get_resources(self):
|
@@ -283,10 +460,12 @@ class Lecture:
|
|
283
460
|
Returns:
|
284
461
|
Os recursos adicionais relacionados à aula.
|
285
462
|
"""
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
463
|
+
if self.__additional_files:
|
464
|
+
files_add = get_files_aule(lecture_id_filter=self.get_lecture_id, data=self.__additional_files)
|
465
|
+
f = Files(files=files_add, id_course=self.__course_id).get_download_url
|
466
|
+
return f
|
467
|
+
else:
|
468
|
+
return []
|
290
469
|
|
291
470
|
class Course:
|
292
471
|
"""Recebe um dicionário com os dados do curso."""
|
@@ -299,8 +478,8 @@ class Course:
|
|
299
478
|
results (dict): Um dicionário contendo os dados do curso.
|
300
479
|
course_id (int): O ID do curso.
|
301
480
|
"""
|
302
|
-
self.__parser_chapers =
|
303
|
-
self.__data = self.__parser_chapers
|
481
|
+
self.__parser_chapers = parser_chapters(results=results)
|
482
|
+
self.__data:list = self.__parser_chapers
|
304
483
|
self.__course_id = course_id
|
305
484
|
self.__results = results
|
306
485
|
self.__additional_files_data = get_add_files(course_id)
|
@@ -375,8 +554,8 @@ class Course:
|
|
375
554
|
int: O número total de lectures no curso.
|
376
555
|
"""
|
377
556
|
total_lectures = 0
|
378
|
-
for chapter in self.__data
|
379
|
-
total_lectures += len(chapter.get('
|
557
|
+
for chapter in self.__data:
|
558
|
+
total_lectures += len(chapter.get('lectures', []))
|
380
559
|
return total_lectures
|
381
560
|
|
382
561
|
@property
|
@@ -398,39 +577,35 @@ class Course:
|
|
398
577
|
list: Uma lista contendo os títulos de vídeos no curso.
|
399
578
|
"""
|
400
579
|
videos = []
|
401
|
-
for chapter in self.__data
|
580
|
+
for chapter in self.__data:
|
402
581
|
for video in chapter.get('videos_in_chapter', []):
|
403
|
-
|
404
|
-
if
|
405
|
-
|
582
|
+
asset_type = video.get('asset_type')
|
583
|
+
if asset_type == 'Video':
|
584
|
+
title = video['title']
|
585
|
+
if title != "Files":
|
586
|
+
videos.append(title)
|
406
587
|
return videos
|
407
588
|
|
408
589
|
@property
|
409
590
|
def get_lectures(self) -> list:
|
410
591
|
"""
|
411
|
-
Obtém uma lista com todas as aulas.
|
412
|
-
|
592
|
+
Obtém uma lista de dicionários com todas as aulas.
|
413
593
|
Returns:
|
414
594
|
list: Uma lista contendo todas as aulas.
|
415
595
|
"""
|
416
596
|
videos = []
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
for index, video in enumerate(chapter.get('videos_in_chapter', [])):
|
421
|
-
section = f"{chapter.get('title_chapter')}" # Adicionar numeração da Sessão
|
422
|
-
title = video.get('video_title')
|
423
|
-
id_lecture = video.get('lecture_id')
|
424
|
-
id_asset = video.get('asset_id')
|
597
|
+
|
598
|
+
for chapter in self.__data:
|
599
|
+
for video in chapter.get('lectures', []):
|
425
600
|
dt = {
|
426
|
-
'section':
|
427
|
-
'title': title,
|
428
|
-
'lecture_id':
|
429
|
-
'asset_id':
|
430
|
-
'
|
601
|
+
'section': chapter.get('title', ''),
|
602
|
+
'title': video.get('title', ''),
|
603
|
+
'lecture_id': video.get('lecture_id', ''),
|
604
|
+
'asset_id': video.get('asset_id', ''),
|
605
|
+
'asset_type': video.get('asset_type', '')
|
431
606
|
}
|
432
607
|
videos.append(dt)
|
433
|
-
|
608
|
+
|
434
609
|
return videos
|
435
610
|
|
436
611
|
def get_details_lecture(self, lecture_id: int) -> Lecture:
|
@@ -443,7 +618,19 @@ class Course:
|
|
443
618
|
Returns:
|
444
619
|
Lecture: Um objeto Lecture contendo os detalhes da aula.
|
445
620
|
"""
|
446
|
-
|
621
|
+
type_lecture = ''
|
622
|
+
links= {}
|
623
|
+
if not is_lecture_in_course(lecture_id=lecture_id,lectures=self.get_lectures):
|
624
|
+
raise FileNotFoundError(
|
625
|
+
'Essa aula não existe nesse curso!'
|
626
|
+
)
|
627
|
+
for l in self.get_lectures:
|
628
|
+
if lecture_id == l.get('lecture_id'):
|
629
|
+
type_lecture = l.get('asset_type')
|
630
|
+
if type_lecture.lower() == 'video' or type_lecture.lower() == 'article':
|
631
|
+
links = get_links(course_id=self.__course_id, id_lecture=lecture_id)
|
632
|
+
else:
|
633
|
+
links = get_assessments(course_id=self.__course_id,lecture_id=lecture_id)
|
447
634
|
additional_files = self.__load_assets()
|
448
635
|
lecture = Lecture(data=links, course_id=self.__course_id, additional_files=additional_files)
|
449
636
|
return lecture
|
@@ -84,7 +84,8 @@ def get_details_courses(course_id):
|
|
84
84
|
resposta = json.loads(response.text)
|
85
85
|
return resposta
|
86
86
|
else:
|
87
|
-
raise UdemyUserApiExceptions(
|
87
|
+
raise UdemyUserApiExceptions(
|
88
|
+
response.text)
|
88
89
|
|
89
90
|
|
90
91
|
def get_course_infor(course_id):
|
@@ -19,9 +19,6 @@ class Udemy:
|
|
19
19
|
LoginException: Se a sessão estiver expirada.
|
20
20
|
"""
|
21
21
|
self.__headers = HEADERS_USER
|
22
|
-
if not verif_login:
|
23
|
-
raise LoginException("Sessão expirada!")
|
24
|
-
|
25
22
|
@staticmethod
|
26
23
|
def my_subscribed_courses_by_plan() -> list[dict]:
|
27
24
|
"""
|
@@ -33,6 +30,10 @@ class Udemy:
|
|
33
30
|
Raises:
|
34
31
|
UdemyUserApiExceptions: Se houver erro ao obter os cursos.
|
35
32
|
"""
|
33
|
+
if not verif_login:
|
34
|
+
raise LoginException(
|
35
|
+
"Nenhuma sessão ativa,primeiro efetue login!")
|
36
|
+
|
36
37
|
try:
|
37
38
|
courses = get_courses_plan(tipe='plan')
|
38
39
|
return courses
|
@@ -50,6 +51,10 @@ class Udemy:
|
|
50
51
|
Raises:
|
51
52
|
UdemyUserApiExceptions: Se houver erro ao obter os cursos.
|
52
53
|
"""
|
54
|
+
if not verif_login:
|
55
|
+
raise LoginException(
|
56
|
+
"Nenhuma sessão ativa,primeiro efetue login!")
|
57
|
+
|
53
58
|
try:
|
54
59
|
# Obtém os cursos
|
55
60
|
courses1 = get_courses_plan(tipe='default') # lista de cursos padrão
|
@@ -72,7 +77,7 @@ class Udemy:
|
|
72
77
|
raise UnhandledExceptions(e)
|
73
78
|
|
74
79
|
@staticmethod
|
75
|
-
def get_details_course(course_id):
|
80
|
+
def get_details_course(course_id) ->Course:
|
76
81
|
"""
|
77
82
|
Obtém detalhes de um curso através do ID.
|
78
83
|
|
@@ -85,6 +90,10 @@ class Udemy:
|
|
85
90
|
Raises:
|
86
91
|
UnhandledExceptions: Se houver erro ao obter os detalhes do curso.
|
87
92
|
"""
|
93
|
+
if not verif_login:
|
94
|
+
raise LoginException(
|
95
|
+
"Nenhuma sessão ativa,primeiro efetue login!")
|
96
|
+
|
88
97
|
try:
|
89
98
|
d = get_details_courses(course_id)
|
90
99
|
b = Course(course_id=course_id, results=d)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: udemy_userAPI
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.7
|
4
4
|
Summary: Obtenha detalhes de cursos que o usuário esteja inscrito da plataforma Udemy,usando o EndPoint de usuário o mesmo que o navegador utiliza para acessar e redenrizar os cursos.
|
5
5
|
Author: PauloCesar-dev404
|
6
6
|
Author-email: paulocesar0073dev404@gmail.com
|
@@ -15,11 +15,21 @@ License-File: LICENSE
|
|
15
15
|
Requires-Dist: requests
|
16
16
|
Requires-Dist: cloudscraper
|
17
17
|
Requires-Dist: pywidevine
|
18
|
+
Dynamic: author
|
19
|
+
Dynamic: author-email
|
20
|
+
Dynamic: description
|
21
|
+
Dynamic: description-content-type
|
22
|
+
Dynamic: keywords
|
23
|
+
Dynamic: license
|
24
|
+
Dynamic: platform
|
25
|
+
Dynamic: project-url
|
26
|
+
Dynamic: requires-dist
|
27
|
+
Dynamic: summary
|
18
28
|
|
19
29
|
# udemy-userAPI
|
20
30
|
|
21
31
|
|
22
|
-

|
23
33
|

|
24
34
|
[](https://paulocesar-dev404.github.io/me-apoiando-online/)
|
25
35
|
[](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|