udemy-userAPI 0.3.10__tar.gz → 0.3.12__tar.gz

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.
Files changed (25) hide show
  1. {udemy_userapi-0.3.10/udemy_userAPI.egg-info → udemy_userapi-0.3.12}/PKG-INFO +5 -4
  2. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/README.md +1 -1
  3. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/README_PYPI.md +2 -2
  4. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/__version__.py +1 -1
  5. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/api.py +1 -1
  6. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/authenticate.py +147 -27
  7. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12/udemy_userAPI.egg-info}/PKG-INFO +5 -4
  8. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/LICENSE +0 -0
  9. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/MANIFEST.in +0 -0
  10. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/setup.cfg +0 -0
  11. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/setup.py +0 -0
  12. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/.cache/.udemy_userAPI +0 -0
  13. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/__init__.py +0 -0
  14. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/bultins.py +0 -0
  15. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/exeptions.py +0 -0
  16. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/mpd_analyzer/__init__.py +0 -0
  17. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/mpd_analyzer/bin.wvd +0 -0
  18. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/mpd_analyzer/mpd_parser.py +0 -0
  19. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/sections.py +0 -0
  20. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI/udemy.py +0 -0
  21. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI.egg-info/SOURCES.txt +0 -0
  22. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI.egg-info/dependency_links.txt +0 -0
  23. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI.egg-info/not-zip-safe +0 -0
  24. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI.egg-info/requires.txt +0 -0
  25. {udemy_userapi-0.3.10 → udemy_userapi-0.3.12}/udemy_userAPI.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: udemy_userAPI
3
- Version: 0.3.10
3
+ Version: 0.3.12
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
@@ -21,6 +21,7 @@ Dynamic: description
21
21
  Dynamic: description-content-type
22
22
  Dynamic: keywords
23
23
  Dynamic: license
24
+ Dynamic: license-file
24
25
  Dynamic: platform
25
26
  Dynamic: project-url
26
27
  Dynamic: requires-dist
@@ -29,10 +30,10 @@ Dynamic: summary
29
30
  # udemy-userAPI
30
31
 
31
32
 
32
- ![Versão](https://img.shields.io/badge/version-0.3.8-orange)
33
+ ![Versão](https://img.shields.io/badge/version-0.3.12-orange)
33
34
  ![Licença](https://img.shields.io/badge/license-MIT-orange)
34
35
  [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://paulocesar-dev404.github.io/me-apoiando-online/)
35
- [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
36
+ [![Sponsor](https://img.shields.io/badge/Documentation-green)]()
36
37
 
37
38
 
38
39
  Obtenha detalhes de cursos da plataforma udemy com a api de usuário,usando esta lib
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- ![Versão](https://img.shields.io/badge/version-0.3.8-orange)
6
+ ![Versão](https://img.shields.io/badge/version-0.3.11-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,10 +1,10 @@
1
1
  # udemy-userAPI
2
2
 
3
3
 
4
- ![Versão](https://img.shields.io/badge/version-0.3.8-orange)
4
+ ![Versão](https://img.shields.io/badge/version-0.3.12-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
- [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
7
+ [![Sponsor](https://img.shields.io/badge/Documentation-green)]()
8
8
 
9
9
 
10
10
  Obtenha detalhes de cursos da plataforma udemy com a api de usuário,usando esta lib
@@ -1,4 +1,4 @@
1
- __version__ = '0.3.10'
1
+ __version__ = '0.3.12'
2
2
  __lib_name__ = 'udemy_userAPI' # local name
3
3
  __repo_name__ = 'udemy-userAPI'
4
4
  __autor__ = 'PauloCesar-dev404'
@@ -14,7 +14,7 @@ import base64
14
14
 
15
15
 
16
16
  AUTH = UdemyAuth()
17
- COOKIES = AUTH._load_cookies()
17
+ COOKIES = AUTH.load_cookies()
18
18
 
19
19
  HEADERS_USER = {
20
20
  "accept": "*/*",
@@ -1,14 +1,47 @@
1
+ import http
1
2
  import json
2
3
  import os
3
4
  import pickle
4
5
  import traceback
6
+ from http.cookies import SimpleCookie
7
+
8
+ import cloudscraper
5
9
  import requests
10
+
6
11
  from .exeptions import UnhandledExceptions, UdemyUserApiExceptions, LoginException, Upstreamconnecterror
7
- import cloudscraper
8
12
 
9
13
  DEBUG = False
10
14
 
11
15
 
16
+ def convert_cook(cookie_string):
17
+ """
18
+ Converte uma string de cookies para um dicionário usando http.cookies.
19
+
20
+ Args:
21
+ cookie_string (str): A string de cookies no formato "nome1=valor1; nome2=valor2".
22
+
23
+ Returns:
24
+ dict: Um dicionário onde as chaves são os nomes dos cookies e os valores são os seus respectivos valores.
25
+ Retorna um dicionário vazio se a string for inválida ou vazia.
26
+ """
27
+ if not isinstance(cookie_string, str) or not cookie_string.strip():
28
+ print("A string de cookies fornecida é inválida ou vazia.")
29
+ return {}
30
+
31
+ cookie = SimpleCookie()
32
+ try:
33
+ # Carrega a string de cookies
34
+ cookie.load(cookie_string)
35
+
36
+ # Converte o objeto SimpleCookie para um dicionário regular
37
+ dicionario_cookies = {k: v.value for k, v in cookie.items()}
38
+
39
+ return dicionario_cookies
40
+ except http.cookies.CookieError as e:
41
+ print(f"Erro ao analisar a string de cookies: {e}")
42
+ return {}
43
+
44
+
12
45
  class UdemyAuth:
13
46
  def __init__(self):
14
47
  """
@@ -162,7 +195,7 @@ class UdemyAuth:
162
195
 
163
196
  # Verifica a resposta para determinar se o login foi bem-sucedido
164
197
  if "returnUrl" in r.text:
165
- self._save_cookies(s.cookies)
198
+ self.__save_cookies(s.cookies)
166
199
  else:
167
200
  login_error = r.json().get("error", {}).get("data", {}).get("formErrors", [])[0]
168
201
  if login_error[0] == "Y":
@@ -176,14 +209,101 @@ class UdemyAuth:
176
209
  e = traceback.format_exc()
177
210
  raise LoginException(e)
178
211
 
179
- def _save_cookies(self, cookies):
212
+ def login_direct_cookies(self, cookies: str):
213
+ """
214
+ Realiza login via cookies diretamente. (Não é seguro para uso em produção).
215
+
216
+ Argumentos:
217
+ cookies: Pode ser o caminho do arquivo cookies (apenas .json ou .txt)
218
+ ou a string com os cookies.
219
+
220
+ Raises:
221
+ FileNotFoundError: Se o arquivo especificado não for encontrado.
222
+ ValueError: Se o arquivo não for .json ou .txt, ou se o JSON for inválido.
223
+ UnhandledExceptions: Para outros erros inesperados durante a leitura do arquivo.
224
+ LoginException: Se os cookies estiverem expirados, inválidos ou houver falha no login.
225
+ """
226
+ cookies_content = ""
227
+ # Verifica se a string fornecida é um caminho de arquivo existente
228
+ if os.path.isfile(cookies):
229
+ file_extension = os.path.splitext(cookies)[1].lower() # Pega a extensão do arquivo
230
+
231
+ # Verifica se a extensão é permitida
232
+ if file_extension not in ['.json', '.txt']:
233
+ raise ValueError(
234
+ f"Erro: Apenas arquivos .json ou .txt são permitidos. "
235
+ f"Extensão recebida: '{file_extension}' para o arquivo '{cookies}'."
236
+ )
237
+
238
+ try:
239
+ with open(cookies, 'r', encoding='utf-8') as f:
240
+ cookies_content = f.read()
241
+
242
+ # Se for um arquivo JSON, tenta carregá-lo para validar
243
+ if file_extension == '.json':
244
+ try:
245
+ # Tenta carregar o JSON. Se for um JSON de cookies, ele pode estar
246
+ # em um formato específico. Aqui estamos apenas validando a sintaxe JSON.
247
+ json.loads(cookies_content)
248
+ except json.JSONDecodeError as e:
249
+ raise ValueError(f"Erro: O arquivo '{cookies}' contém JSON inválido: {e}")
250
+
251
+ except FileNotFoundError:
252
+ raise FileNotFoundError(f"Erro: O arquivo '{cookies}' não foi encontrado.")
253
+ except Exception as e:
254
+ raise UnhandledExceptions(f"Erro ao ler o arquivo '{cookies}': {e}")
255
+ else:
256
+ # Se não for um arquivo, assume-se que é a string de cookies diretamente
257
+ cookies_content = cookies
258
+
259
+ headers = {
260
+ "accept": "*/*",
261
+ "accept-language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
262
+ "cache-control": "no-cache",
263
+ "pragma": "no-cache",
264
+ "sec-ch-ua": '"Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"',
265
+ "sec-ch-ua-mobile": "?0",
266
+ "sec-ch-ua-platform": '"Windows"',
267
+ "sec-fetch-dest": "empty",
268
+ "sec-fetch-mode": "cors",
269
+ "sec-fetch-site": "cross-site",
270
+ "Cookie": cookies_content,
271
+ "Referer": "https://www.udemy.com/"
272
+ }
273
+ url = 'https://www.udemy.com/api-2.0/contexts/me/?header=true'
274
+
275
+ try:
276
+ resp = requests.get(url=url, headers=headers)
277
+ resp.raise_for_status() # Lança um HTTPError para respostas de status de erro (4xx ou 5xx)
278
+
279
+ convert = resp.json() # Usa resp.json() para parsear diretamente o JSON
280
+ is_logged_in = convert.get('header', {}).get('isLoggedIn', None)
281
+
282
+ if not is_logged_in:
283
+ raise LoginException(
284
+ "Cookies expirados ou inválidos!")
285
+ self.__save_cookies(resp.cookies)
286
+
287
+ except requests.exceptions.HTTPError as e:
288
+ # Captura erros HTTP (ex: 401 Unauthorized, 403 Forbidden)
289
+ raise LoginException(f"Erro de HTTP durante o login: {e}. Resposta: {e.response.text}")
290
+ except requests.exceptions.ConnectionError as e:
291
+ raise UnhandledExceptions(f"Erro de conexão: {e}")
292
+ except requests.exceptions.Timeout as e:
293
+ raise UnhandledExceptions(f"Tempo limite da requisição excedido: {e}")
294
+ except json.JSONDecodeError as e:
295
+ raise UnhandledExceptions(f"Erro ao decodificar JSON da resposta da API: {e}. Resposta: {resp.text}")
296
+ except Exception as e:
297
+ raise UnhandledExceptions(f"Um erro inesperado ocorreu durante o login: {e}")
298
+
299
+ def __save_cookies(self, cookies):
180
300
  try:
181
301
  with open(fr'{self.__file_path}', 'wb') as f:
182
302
  pickle.dump(cookies, f)
183
303
  except Exception as e:
184
304
  raise LoginException(e)
185
305
 
186
- def _load_cookies(self) -> str:
306
+ def load_cookies(self) -> str:
187
307
  """Carrega cookies e retorna-os em uma string formatada"""
188
308
  try:
189
309
  file = os.path.join(self.__file_path)
@@ -208,7 +328,7 @@ class UdemyAuth:
208
328
  with open(self.__file_path, 'wb') as f:
209
329
  f.write(b'')
210
330
 
211
- def login_passwordless(self, email: str, locale: str = 'pt-BR'):
331
+ def login_passwordless(self, email: str, locale: str = 'pt-BR', otp_callback=None):
212
332
  """
213
333
  Realiza login na Udemy usando autenticação de dois fatores (2FA).
214
334
 
@@ -219,6 +339,7 @@ class UdemyAuth:
219
339
  Args:
220
340
  email (str): Email do usuário.
221
341
  locale (str): Localização do usuário (recomendado para receber mensagens no idioma local).
342
+ otp_callback (callable, opcional): Função para obter o código OTP (se None, usa input padrão).
222
343
 
223
344
  Raises:
224
345
  LoginException: Em caso de falha no processo de login.
@@ -227,23 +348,16 @@ class UdemyAuth:
227
348
  try:
228
349
  if self.verif_login():
229
350
  raise UserWarning("Atenção, você já possui uma Sessão válida!")
230
- # Inicializa uma sessão com proteção contra Cloudflare
231
- session = cloudscraper.create_scraper()
232
351
 
233
- # Requisita a página de inscrição para obter o token CSRF
352
+ session = cloudscraper.create_scraper()
234
353
  signup_url = "https://www.udemy.com/join/signup-popup/"
235
354
  headers = {"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"}
236
355
  response = session.get(signup_url, headers=headers)
237
-
238
- # Obtém o token CSRF dos cookies retornados
239
356
  csrf_token = response.cookies.get("csrftoken")
240
357
  if not csrf_token:
241
358
  raise LoginException("Não foi possível obter o token CSRF.")
242
359
 
243
- # Prepara os dados do login
244
360
  data = {"email": email, "fullname": ""}
245
-
246
- # Atualiza os cookies e cabeçalhos da sessão
247
361
  session.cookies.update(response.cookies)
248
362
  session.headers.update({
249
363
  "User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
@@ -261,17 +375,20 @@ class UdemyAuth:
261
375
  "Cache-Control": "no-cache",
262
376
  })
263
377
 
264
- # Faz a requisição para iniciar o login
265
378
  login_url = "https://www.udemy.com/api-2.0/auth/code-generation/login/4.0/"
266
379
  response = session.post(login_url, data=data, allow_redirects=False)
380
+
267
381
  if 'error_message' in response.text:
268
382
  erro_data: dict = response.json()
269
383
  error_message = erro_data.get('error_message', {})
270
384
  raise LoginException(error_message)
385
+
271
386
  for attempt in range(3):
272
- # Solicita o código OTP ao usuário
273
- otp = input("Digite o código de 6 dígitos enviado ao seu e-mail: ")
274
- # Realiza o login com o código OTP
387
+ # Obtém o código OTP via callback ou terminal
388
+ if otp_callback and callable(otp_callback):
389
+ otp = otp_callback()
390
+ else:
391
+ otp = input("Digite o código de 6 dígitos enviado ao seu e-mail: ")
275
392
  otp_login_url = "https://www.udemy.com/api-2.0/auth/udemy-passwordless/login/4.0/"
276
393
  otp_data = {
277
394
  "email": email,
@@ -280,33 +397,36 @@ class UdemyAuth:
280
397
  "subscribeToEmails": "false",
281
398
  "upow": J(email, 'login')
282
399
  }
400
+
283
401
  session.headers.update({
284
402
  "Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale}&next="
285
403
  f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html"
286
404
  })
405
+
287
406
  response = session.post(otp_login_url, otp_data, allow_redirects=False)
288
- # Verifica se o login foi bem-sucedido
407
+
289
408
  if response.status_code == 200:
290
- self._save_cookies(session.cookies)
409
+ self.__save_cookies(session.cookies)
410
+ break # Sai do loop se o login for bem-sucedido
291
411
  else:
292
412
  if 'error_message' in response.text:
293
413
  erro_data: dict = response.json()
294
414
  error_message = erro_data.get('error_message', {})
295
415
  error_code = erro_data.get('error_code', {})
416
+
296
417
  if error_code == '1538':
297
418
  raise LoginException(error_message)
298
419
  elif error_code == '2550':
299
- print(error_message)
300
- continue
420
+ ### codigo errado....
421
+ raise LoginException(error_message)
301
422
  elif error_code == '1330':
302
423
  raise LoginException(error_message)
303
424
  elif error_code == '1149':
304
- raise LoginException(f"Erro interno ao enviar os dados veja os detalhes: '{error_message}'")
425
+ raise LoginException(
426
+ f"Erro interno ao enviar os dados, veja os detalhes: '{error_message}'")
427
+
305
428
  raise LoginException(response.text)
306
- break
429
+
307
430
  except Exception as e:
308
- if DEBUG:
309
- error_details = traceback.format_exc()
310
- else:
311
- error_details = str(e)
431
+ error_details = traceback.format_exc() if DEBUG else str(e)
312
432
  raise LoginException(error_details)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: udemy_userAPI
3
- Version: 0.3.10
3
+ Version: 0.3.12
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
@@ -21,6 +21,7 @@ Dynamic: description
21
21
  Dynamic: description-content-type
22
22
  Dynamic: keywords
23
23
  Dynamic: license
24
+ Dynamic: license-file
24
25
  Dynamic: platform
25
26
  Dynamic: project-url
26
27
  Dynamic: requires-dist
@@ -29,10 +30,10 @@ Dynamic: summary
29
30
  # udemy-userAPI
30
31
 
31
32
 
32
- ![Versão](https://img.shields.io/badge/version-0.3.8-orange)
33
+ ![Versão](https://img.shields.io/badge/version-0.3.12-orange)
33
34
  ![Licença](https://img.shields.io/badge/license-MIT-orange)
34
35
  [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://paulocesar-dev404.github.io/me-apoiando-online/)
35
- [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
36
+ [![Sponsor](https://img.shields.io/badge/Documentation-green)]()
36
37
 
37
38
 
38
39
  Obtenha detalhes de cursos da plataforma udemy com a api de usuário,usando esta lib
File without changes
File without changes
File without changes