udemy-userAPI 0.3.7__tar.gz → 0.3.9__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. {udemy_userapi-0.3.7/udemy_userAPI.egg-info → udemy_userapi-0.3.9}/PKG-INFO +2 -2
  2. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/README.md +1 -1
  3. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/README_PYPI.md +1 -1
  4. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/__version__.py +1 -1
  5. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/api.py +52 -30
  6. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/bultins.py +6 -120
  7. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/sections.py +42 -17
  8. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/udemy.py +4 -13
  9. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9/udemy_userAPI.egg-info}/PKG-INFO +2 -2
  10. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/LICENSE +0 -0
  11. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/MANIFEST.in +0 -0
  12. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/setup.cfg +0 -0
  13. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/setup.py +0 -0
  14. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/.cache/.udemy_userAPI +0 -0
  15. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/__init__.py +0 -0
  16. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/authenticate.py +0 -0
  17. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/exeptions.py +0 -0
  18. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/mpd_analyzer/__init__.py +0 -0
  19. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/mpd_analyzer/bin.wvd +0 -0
  20. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI/mpd_analyzer/mpd_parser.py +0 -0
  21. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI.egg-info/SOURCES.txt +0 -0
  22. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI.egg-info/dependency_links.txt +0 -0
  23. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI.egg-info/not-zip-safe +0 -0
  24. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI.egg-info/requires.txt +0 -0
  25. {udemy_userapi-0.3.7 → udemy_userapi-0.3.9}/udemy_userAPI.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: udemy_userAPI
3
- Version: 0.3.7
3
+ Version: 0.3.9
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
@@ -29,7 +29,7 @@ Dynamic: summary
29
29
  # udemy-userAPI
30
30
 
31
31
 
32
- ![Versão](https://img.shields.io/badge/version-0.3.7-orange)
32
+ ![Versão](https://img.shields.io/badge/version-0.3.8-orange)
33
33
  ![Licença](https://img.shields.io/badge/license-MIT-orange)
34
34
  [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://paulocesar-dev404.github.io/me-apoiando-online/)
35
35
  [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- ![Versão](https://img.shields.io/badge/version-0.3.7-orange)
6
+ ![Versão](https://img.shields.io/badge/version-0.3.8-orange)
7
7
  ![Licença](https://img.shields.io/badge/license-MIT-orange)
8
8
  [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://paulocesar-dev404.github.io/me-apoiando-online/)
9
9
  [![Sponsor](https://img.shields.io/badge/Documentation-green)](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
- ![Versão](https://img.shields.io/badge/version-0.3.7-orange)
4
+ ![Versão](https://img.shields.io/badge/version-0.3.8-orange)
5
5
  ![Licença](https://img.shields.io/badge/license-MIT-orange)
6
6
  [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://paulocesar-dev404.github.io/me-apoiando-online/)
7
7
  [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
@@ -1,4 +1,4 @@
1
- __version__ = '0.3.7'
1
+ __version__ = '0.3.9'
2
2
  __lib_name__ = 'udemy_userAPI' # local name
3
3
  __repo_name__ = 'udemy-userAPI'
4
4
  __autor__ = 'PauloCesar-dev404'
@@ -186,53 +186,75 @@ def get_mpd_file(mpd_url):
186
186
 
187
187
 
188
188
  def parser_chapters(results) -> list[dict]:
189
+ """
190
+ Processa os dados do curso e retorna uma lista de capítulos com suas aulas e quizzes.
191
+
192
+ Se os resultados não contiverem capítulos (i.e. apenas aulas ou quizzes), todas as
193
+ aulas/quizzes serão agrupadas em um capítulo padrão.
194
+
195
+ Args:
196
+ results (dict): Dicionário com os resultados do curso, normalmente contendo a chave 'results'.
197
+
198
+ Returns:
199
+ list[dict]: Lista de capítulos, cada um com título, índice (se disponível) e lista de lectures/quizzes.
200
+
201
+ Raises:
202
+ UdemyUserApiExceptions: Se não for possível obter os detalhes do curso.
203
+ """
189
204
  if not results:
190
205
  raise UdemyUserApiExceptions("Não foi possível obter detalhes do curso!")
191
206
 
192
- results = results.get('results', [])
193
- chapters_dicts = [] # Lista que armazena todos os capítulos
207
+ items = results.get('results', [])
208
+ chapters_dicts = [] # Lista de capítulos
194
209
  current_chapter = None # Capítulo atual
195
210
 
196
- for dictionary in results:
211
+ # Nome padrão para o grupo quando não houver capítulos
212
+ default_chapter_title = "CourseFiles"
213
+
214
+ for dictionary in items:
197
215
  _class = dictionary.get('_class')
198
- chapter_index = dictionary.get('object_index')
216
+ chapter_index = dictionary.get('object_index', None)
199
217
 
200
- # Quando encontrar um novo capítulo
201
218
  if _class == 'chapter':
202
- if current_chapter: # Se já há um capítulo atual, adicionamos
219
+ # Se já há um capítulo em andamento, adiciona-o à lista
220
+ if current_chapter:
203
221
  chapters_dicts.append(current_chapter)
204
-
205
222
  # Inicia um novo capítulo
206
223
  current_chapter = {
207
- 'title': dictionary.get('title'),
224
+ 'title': dictionary.get('title', 'Sem Título'),
208
225
  'chapter_index': chapter_index,
209
226
  'lectures': [] # Lista para armazenar aulas e quizzes
210
227
  }
211
-
212
- # Se for uma aula, adiciona ao capítulo atual
213
- elif _class == 'lecture' and current_chapter:
214
- asset = dictionary.get('asset')
215
- if asset:
216
- lecture_data = {
217
- 'asset_type': asset.get('asset_type', ''),
218
- 'title': dictionary.get('title', 'Files'),
228
+ elif _class in ('lecture', 'quiz'):
229
+ # Se não houver um capítulo atual, cria um capítulo padrão
230
+ if current_chapter is None:
231
+ current_chapter = {
232
+ 'title': default_chapter_title,
233
+ 'chapter_index': None,
234
+ 'lectures': []
235
+ }
236
+ # Processa a aula ou quiz
237
+ if _class == 'lecture':
238
+ asset = dictionary.get('asset')
239
+ if asset:
240
+ lecture_data = {
241
+ 'asset_type': asset.get('asset_type', ''),
242
+ 'title': dictionary.get('title', 'Aula'),
243
+ 'lecture_id': dictionary.get('id', ''),
244
+ 'asset_id': asset.get('id', '')
245
+ }
246
+ current_chapter['lectures'].append(lecture_data)
247
+ elif _class == 'quiz':
248
+ quiz_data = {
249
+ 'asset_type': 'quiz',
250
+ 'title': dictionary.get('title', 'Quiz'),
219
251
  'lecture_id': dictionary.get('id', ''),
220
- 'asset_id': asset.get('id', '')
252
+ 'type': dictionary.get('type', ''),
253
+ 'asset_id': ''
221
254
  }
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)
255
+ current_chapter['lectures'].append(quiz_data)
234
256
 
235
- # Adiciona o último capítulo processado
257
+ # Se houver um capítulo em andamento, adiciona-o à lista
236
258
  if current_chapter:
237
259
  chapters_dicts.append(current_chapter)
238
260
 
@@ -162,115 +162,6 @@ class Quiz:
162
162
  htmls = get_quizzes(lecture_id=self.id)
163
163
  return htmls
164
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
-
273
-
274
165
  class Lecture:
275
166
  """Cria objetos aula (lecture) do curso e extrai os dados."""
276
167
 
@@ -353,20 +244,15 @@ class Lecture:
353
244
  return self.__asset.get('media_sources',[])
354
245
 
355
246
  @property
356
- def get_captions(self) -> Captions:
247
+ def get_captions(self) -> list:
357
248
  """
358
249
  Obtém as legendas.
359
250
 
360
251
  Returns:
361
- Captions: Objeto para gerenciar as legendas.
252
+ list: Uma lista contendo as legendas.
362
253
  """
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
- )
254
+ return self.__asset.get('captions',[])
255
+
370
256
  @property
371
257
  def get_external_url(self) -> list:
372
258
  """
@@ -602,12 +488,12 @@ class Course:
602
488
  'title': video.get('title', ''),
603
489
  'lecture_id': video.get('lecture_id', ''),
604
490
  'asset_id': video.get('asset_id', ''),
605
- 'asset_type': video.get('asset_type', '')
491
+ 'asset_type': video.get('asset_type', ''),
492
+ 'section_order': chapter.get('chapter_index',1)
606
493
  }
607
494
  videos.append(dt)
608
495
 
609
496
  return videos
610
-
611
497
  def get_details_lecture(self, lecture_id: int) -> Lecture:
612
498
  """
613
499
  Obtém detalhes de uma aula específica.
@@ -53,13 +53,13 @@ def get_courses_plan(tipe: str) -> list:
53
53
 
54
54
  def get_details_courses(course_id):
55
55
  """
56
- Obtém detalhes de um curso específico.
56
+ Obtém detalhes de um curso específico, realizando paginação caso haja múltiplas páginas.
57
57
 
58
58
  Args:
59
59
  course_id (int): ID do curso.
60
60
 
61
61
  Returns:
62
- dict: Dicionário contendo os detalhes do curso.
62
+ dict: Dicionário contendo os detalhes do curso com todos os itens concatenados.
63
63
 
64
64
  Raises:
65
65
  LoginException: Se a sessão estiver expirada.
@@ -70,22 +70,47 @@ def get_details_courses(course_id):
70
70
  auth = UdemyAuth()
71
71
  if not auth.verif_login():
72
72
  raise LoginException("Sessão expirada!")
73
- response = requests.get(
73
+
74
+ # URL base com parâmetros
75
+ base_url = (
74
76
  f"https://www.udemy.com/api-2.0/courses/{course_id}/subscriber-curriculum-items/?"
75
- f"caching_intent=True&fields%5Basset%5D=title%2Cfilename%2Casset_type%2Cstatus%2Ctime_estimation%2"
76
- f"Cis_external&fields%5Bchapter%5D=title%2Cobject_index%2Cis_published%2Csort_order&fields%5Blecture"
77
- f"%5D=title%2Cobject_index%2Cis_published%2Csort_order%2Ccreated%2Casset%2Csupplementary_assets%2"
78
- f"Cis_free&fields%5Bpractice%5D=title%2Cobject_index%2Cis_published%2Csort_order&fields%5Bquiz%5D="
79
- f"title%2Cobject_index%2Cis_published%2Csort_order%2Ctype&pages&page_size=400&fields[lecture]=asset,"
80
- f"description,download_url,is_free,last_watched_second&fields[asset]=asset_type,length,"
81
- f"media_license_token,course_is_drmed,external_url&q=0.3108014137011559",
82
- headers=HEADERS_USER)
83
- if response.status_code == 200:
84
- resposta = json.loads(response.text)
85
- return resposta
86
- else:
87
- raise UdemyUserApiExceptions(
88
- response.text)
77
+ f"page_size=1000&"
78
+ f"fields[lecture]=title,object_index,is_published,sort_order,created,asset,supplementary_assets,is_free&"
79
+ f"fields[quiz]=title,object_index,is_published,sort_order,type&"
80
+ f"fields[practice]=title,object_index,is_published,sort_order&"
81
+ f"fields[chapter]=title,object_index,is_published,sort_order&"
82
+ f"fields[asset]=title,filename,asset_type,status,time_estimation,is_external&"
83
+ f"caching_intent=True"
84
+ )
85
+
86
+ try:
87
+ response = requests.get(base_url, headers=HEADERS_USER)
88
+ if response.status_code != 200:
89
+ raise UdemyUserApiExceptions(
90
+ f"Erro ao obter detalhes do curso! Código de status: {response.status_code}")
91
+
92
+ data = json.loads(response.text)
93
+ all_results = data.get('results', [])
94
+ next_page = data.get('next', '')
95
+
96
+ # Enquanto houver próxima página, faz requisição e junta os resultados
97
+ while next_page:
98
+ response = requests.get(next_page, headers=HEADERS_USER)
99
+ if response.status_code != 200:
100
+ # Caso ocorra erro na próxima página, pode-se optar por interromper ou registrar o erro.....por enquanto
101
+ # irei parar..mais se por acaso futuramente não der certo mudarei esta implementação!
102
+ # @pauloCesarDev404
103
+ break
104
+ next_data = json.loads(response.text)
105
+ all_results.extend(next_data.get('results', []))
106
+ next_page = next_data.get('next', '')
107
+
108
+ # Atualiza o dicionário final com todos os itens concatenados
109
+ data['results'] = all_results
110
+ return data
111
+
112
+ except Exception as e:
113
+ raise UdemyUserApiExceptions(f"Erro ao obter detalhes do curso! {e}")
89
114
 
90
115
 
91
116
  def get_course_infor(course_id):
@@ -19,6 +19,9 @@ 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
+
22
25
  @staticmethod
23
26
  def my_subscribed_courses_by_plan() -> list[dict]:
24
27
  """
@@ -30,10 +33,6 @@ class Udemy:
30
33
  Raises:
31
34
  UdemyUserApiExceptions: Se houver erro ao obter os cursos.
32
35
  """
33
- if not verif_login:
34
- raise LoginException(
35
- "Nenhuma sessão ativa,primeiro efetue login!")
36
-
37
36
  try:
38
37
  courses = get_courses_plan(tipe='plan')
39
38
  return courses
@@ -51,10 +50,6 @@ class Udemy:
51
50
  Raises:
52
51
  UdemyUserApiExceptions: Se houver erro ao obter os cursos.
53
52
  """
54
- if not verif_login:
55
- raise LoginException(
56
- "Nenhuma sessão ativa,primeiro efetue login!")
57
-
58
53
  try:
59
54
  # Obtém os cursos
60
55
  courses1 = get_courses_plan(tipe='default') # lista de cursos padrão
@@ -77,7 +72,7 @@ class Udemy:
77
72
  raise UnhandledExceptions(e)
78
73
 
79
74
  @staticmethod
80
- def get_details_course(course_id) ->Course:
75
+ def get_details_course(course_id):
81
76
  """
82
77
  Obtém detalhes de um curso através do ID.
83
78
 
@@ -90,10 +85,6 @@ class Udemy:
90
85
  Raises:
91
86
  UnhandledExceptions: Se houver erro ao obter os detalhes do curso.
92
87
  """
93
- if not verif_login:
94
- raise LoginException(
95
- "Nenhuma sessão ativa,primeiro efetue login!")
96
-
97
88
  try:
98
89
  d = get_details_courses(course_id)
99
90
  b = Course(course_id=course_id, results=d)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: udemy_userAPI
3
- Version: 0.3.7
3
+ Version: 0.3.9
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
@@ -29,7 +29,7 @@ Dynamic: summary
29
29
  # udemy-userAPI
30
30
 
31
31
 
32
- ![Versão](https://img.shields.io/badge/version-0.3.7-orange)
32
+ ![Versão](https://img.shields.io/badge/version-0.3.8-orange)
33
33
  ![Licença](https://img.shields.io/badge/license-MIT-orange)
34
34
  [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://paulocesar-dev404.github.io/me-apoiando-online/)
35
35
  [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
File without changes
File without changes
File without changes
File without changes