udemy-userAPI 0.2.5__py3-none-any.whl → 0.2.7__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.
@@ -1,4 +1,4 @@
1
- __version__ = '0.2.5'
1
+ __version__ = '0.2.7'
2
2
  __lib_name__ = 'udemy_userAPI' # local name
3
3
  __repo_name__ = 'udemy-userAPI'
4
4
  __autor__ = 'PauloCesar-dev404'
@@ -2,7 +2,10 @@ import json
2
2
  import os
3
3
  import pickle
4
4
  import traceback
5
-
5
+ import hashlib
6
+ import hmac
7
+ import math
8
+ from datetime import datetime
6
9
  import requests
7
10
  from .exeptions import UnhandledExceptions, UdemyUserApiExceptions, LoginException
8
11
  import cloudscraper
@@ -142,7 +145,7 @@ class UdemyAuth:
142
145
 
143
146
  # Verifica a resposta para determinar se o login foi bem-sucedido
144
147
  if "returnUrl" in r.text:
145
- self.__save_cookies(s.cookies)
148
+ self._save_cookies(s.cookies)
146
149
  else:
147
150
  login_error = r.json().get("error", {}).get("data", {}).get("formErrors", [])[0]
148
151
  if login_error[0] == "Y":
@@ -151,14 +154,12 @@ class UdemyAuth:
151
154
  raise LoginException("Email ou senha incorretos")
152
155
  else:
153
156
  raise UnhandledExceptions(login_error)
154
-
155
- return s
156
157
  except Exception as e:
157
158
  if DEBUG:
158
159
  e = traceback.format_exc()
159
160
  raise LoginException(e)
160
161
 
161
- def __save_cookies(self, cookies):
162
+ def _save_cookies(self, cookies):
162
163
  try:
163
164
  with open(fr'{self.__file_path}', 'wb') as f:
164
165
  pickle.dump(cookies, f)
@@ -188,4 +189,131 @@ class UdemyAuth:
188
189
 
189
190
  def remove_cookies(self):
190
191
  if os.path.exists(self.__file_path):
191
- os.remove(self.__file_path)
192
+ with open(self.__file_path, 'wb') as f:
193
+ f.write(b'')
194
+
195
+ def login_passwordless(self, email: str, locale: str = 'pt-BR'):
196
+ """
197
+ Realiza login na Udemy usando autenticação de dois fatores (2FA).
198
+
199
+ Este método utiliza o fluxo de autenticação OAuth da Udemy para enviar um
200
+ código de verificação por e-mail ao usuário. Após inserir o código recebido,
201
+ o login é concluído.
202
+
203
+ :param email: Email do usuário.
204
+ :param locale: Localização do usuário (recomendado para receber mensagens no idioma local).
205
+ :raises LoginException: Em caso de falha no processo de login.
206
+ """
207
+ try:
208
+ # Inicializa uma sessão com proteção contra Cloudflare
209
+ session = cloudscraper.create_scraper()
210
+
211
+ # Requisita a página de inscrição para obter o token CSRF
212
+ signup_url = "https://www.udemy.com/join/signup-popup/"
213
+ headers = {"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"}
214
+ response = session.get(signup_url, headers=headers)
215
+
216
+ # Obtém o token CSRF dos cookies retornados
217
+ csrf_token = response.cookies.get("csrftoken")
218
+ if not csrf_token:
219
+ raise LoginException("Não foi possível obter o token CSRF.")
220
+
221
+ # Prepara os dados do login
222
+ data = {"email": email, "fullname": ""}
223
+
224
+ # Atualiza os cookies e cabeçalhos da sessão
225
+ session.cookies.update(response.cookies)
226
+ session.headers.update({
227
+ "User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
228
+ "Accept": "application/json, text/plain, */*",
229
+ "Accept-Language": locale,
230
+ "Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale.replace('-', '_')}&next="
231
+ f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html",
232
+ "Origin": "https://www.udemy.com",
233
+ "DNT": "1",
234
+ "Connection": "keep-alive",
235
+ "Sec-Fetch-Dest": "empty",
236
+ "Sec-Fetch-Mode": "cors",
237
+ "Sec-Fetch-Site": "same-origin",
238
+ "Pragma": "no-cache",
239
+ "Cache-Control": "no-cache",
240
+ })
241
+
242
+ # Faz a requisição para iniciar o login
243
+ login_url = "https://www.udemy.com/api-2.0/auth/code-generation/login/4.0/"
244
+ response = session.post(login_url, data=data, allow_redirects=False)
245
+ if 'error_message' in response.text:
246
+ erro_data: dict = response.json()
247
+ error_message = erro_data.get('error_message', {})
248
+ raise LoginException(error_message)
249
+ for attempt in range(3):
250
+ # Solicita o código OTP ao usuário
251
+ otp = input("Digite o código de 6 dígitos enviado ao seu e-mail: ")
252
+ # Realiza o login com o código OTP
253
+ otp_login_url = "https://www.udemy.com/api-2.0/auth/udemy-passwordless/login/4.0/"
254
+ otp_data = {
255
+ "email": email,
256
+ "fullname": "",
257
+ "otp": otp,
258
+ "subscribeToEmails": "false",
259
+ "upow": J(email, 'login')
260
+ }
261
+ session.headers.update({
262
+ "Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale}&next="
263
+ f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html"
264
+ })
265
+ response = session.post(otp_login_url, otp_data, allow_redirects=False)
266
+ # Verifica se o login foi bem-sucedido
267
+ if response.status_code == 200:
268
+ self._save_cookies(session.cookies)
269
+ else:
270
+ if 'error_message' in response.text:
271
+ erro_data: dict = response.json()
272
+ error_message = erro_data.get('error_message', {})
273
+ error_code = erro_data.get('error_code', {})
274
+ if error_code == '1538':
275
+ raise LoginException(error_message)
276
+ elif error_code == '2550':
277
+ print(error_message)
278
+ continue
279
+ elif error_code == '1330':
280
+ raise LoginException(error_message)
281
+ elif error_code == '1149':
282
+ LoginException(f"Erro interno ao enviar os dados veja os detalhes: '{error_message}'")
283
+ raise LoginException(response.text)
284
+ break
285
+ except Exception as e:
286
+ if DEBUG:
287
+ error_details = traceback.format_exc()
288
+ else:
289
+ error_details = str(e)
290
+ raise LoginException(error_details)
291
+
292
+
293
+ def J(e, t):
294
+ r = datetime.now()
295
+ s = r.isoformat()[:10]
296
+ return s + X(e, s, t)
297
+
298
+
299
+ def X(e, t, r):
300
+ s = 0
301
+ while True:
302
+ o = ee(s)
303
+ a = hmac.new(r.encode(), (e + t + o).encode(), hashlib.sha256).digest()
304
+ if te(16, a):
305
+ return o
306
+ s += 1
307
+
308
+
309
+ def ee(e):
310
+ if e < 0:
311
+ return ""
312
+ return ee(e // 26 - 1) + chr(65 + e % 26)
313
+
314
+
315
+ def te(e, t):
316
+ r = math.ceil(e / 8)
317
+ s = t[:r]
318
+ o = ''.join(format(byte, '08b') for byte in s)
319
+ return o.startswith('0' * e)
udemy_userAPI/bultins.py CHANGED
@@ -10,9 +10,10 @@ from .mpd_analyzer import MPDParser
10
10
  class DRM:
11
11
  def __init__(self, license_token: str, get_media_sources: list):
12
12
  self.__mpd_content = None
13
- self.__dash_url = organize_streams(streams=get_media_sources).get('dash')
14
13
  self.__token = license_token
15
- self.get_key_for_lesson()
14
+ self.__dash_url = organize_streams(streams=get_media_sources).get('dash', {})
15
+ if not license_token or not get_media_sources or not self.__dash_url:
16
+ return
16
17
 
17
18
  def get_key_for_lesson(self):
18
19
  """get keys for lesson"""
@@ -28,6 +29,12 @@ class DRM:
28
29
  keys = extract(pssh=pssh, license_token=self.__token)
29
30
  if keys:
30
31
  return keys
32
+ else:
33
+ return None
34
+ else:
35
+ return None
36
+ else:
37
+ return None
31
38
 
32
39
 
33
40
  class Files:
@@ -44,11 +51,13 @@ class Files:
44
51
  lecture_id = files.get('lecture_id', None)
45
52
  asset_id = files.get('asset_id', None)
46
53
  title = files.get("title", None)
47
- lecture_title = files.get('lecture_title')
48
- external_link = files.get('ExternalLink')
54
+ lecture_title = files.get('lecture_title', None)
55
+ external_link = files.get('ExternalLink', None)
49
56
  if external_link:
50
57
  lnk = get_external_liks(course_id=self.__id_course, id_lecture=lecture_id, asset_id=asset_id)
51
- dt_file = {'title-file': title, 'lecture_title': lecture_title, 'lecture_id': lecture_id,
58
+ dt_file = {'title-file': title,
59
+ 'lecture_title': lecture_title,
60
+ 'lecture_id': lecture_id,
52
61
  'external_link': external_link,
53
62
  'data-file': lnk.get('external_url', None)}
54
63
  return dt_file
@@ -59,8 +68,9 @@ class Files:
59
68
  headers=HEADERS_USER)
60
69
  if resp.status_code == 200:
61
70
  da = json.loads(resp.text)
62
- ## para cdaa dict de um fle colocar seu titulo:
63
- dt_file = {'title-file': title, 'lecture_title': lecture_title, 'lecture_id': lecture_id,
71
+ dt_file = {'title-file': title,
72
+ 'lecture_title': lecture_title,
73
+ 'lecture_id': lecture_id,
64
74
  'external_link': external_link,
65
75
  'data-file': da['download_urls']}
66
76
  download_urls.append(dt_file)
@@ -128,12 +138,10 @@ class Lecture:
128
138
  def course_is_drmed(self) -> DRM:
129
139
  """verifica se a aula possui DRM se sim retorna as keys da aula...
130
140
  retorna 'kid:key' or None"""
131
- if self.__asset.get('course_is_drmed'):
141
+ if self.__asset.get('course_is_drmed', {}):
132
142
  d = DRM(license_token=self.get_media_license_token,
133
143
  get_media_sources=self.get_media_sources)
134
144
  return d
135
- else:
136
- return self.__asset.get('course_is_drmed')
137
145
 
138
146
  @property
139
147
  def get_download_urls(self) -> list:
@@ -267,8 +275,8 @@ class Course:
267
275
  for item in self.__additional_files_data.get('results', []):
268
276
  # Check if the item is a lecture with supplementary assets
269
277
  if item.get('_class') == 'lecture':
270
- id = item.get('id')
271
- title = item.get('title')
278
+ id = item.get('id', {})
279
+ title = item.get('title', {})
272
280
  assets = item.get('supplementary_assets', [])
273
281
  for asset in assets:
274
282
  supplementary_assets.append({
udemy_userAPI/udemy.py CHANGED
@@ -12,22 +12,23 @@ class Udemy:
12
12
  """wrapper para api de usuario da plataforma udemy"""
13
13
 
14
14
  def __init__(self):
15
- """
16
- cookies de seção.
17
- """
18
15
  self.__headers = HEADERS_USER
19
16
  if verif_login is None:
20
17
  raise LoginException("User Not Logged!")
21
18
 
22
- def my_subscribed_courses_by_plan(self) -> list[dict]:
23
- """obtém os cursos que o usuário esatá inscrito, obtidos atraves de planos(assinatura)"""
19
+ @staticmethod
20
+ def my_subscribed_courses_by_plan() -> list[dict]:
21
+ """obtém os cursos que o usuário esatá inscrito, obtidos atraves de planos(assinatura)
22
+ :return:
23
+ """
24
24
  try:
25
25
  courses = get_courses_plan(tipe='plan')
26
26
  return courses
27
27
  except UdemyUserApiExceptions as e:
28
28
  UnhandledExceptions(e)
29
29
 
30
- def my_subscribed_courses(self) -> list[dict]:
30
+ @staticmethod
31
+ def my_subscribed_courses() -> list[dict]:
31
32
  """Obtém os cursos que o usuário está inscrito, excluindo listas vazias ou nulas"""
32
33
  try:
33
34
  # Obtém os cursos
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udemy_userAPI
3
- Version: 0.2.5
3
+ Version: 0.2.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
@@ -19,7 +19,7 @@ Requires-Dist: pywidevine
19
19
  # udemy-userAPI
20
20
 
21
21
 
22
- ![Versão](https://img.shields.io/badge/version-0.2.4-orange)
22
+ ![Versão](https://img.shields.io/badge/version-0.2.7-orange)
23
23
  ![Licença](https://img.shields.io/badge/license-MIT-orange)
24
24
  [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://apoia.se/paulocesar-dev404)
25
25
  [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
@@ -11,19 +11,19 @@ m3u8_analyzer/__init__.py,sha256=v7CiVqsCq2YH347C-QR1kHPJtXFFdru8qole3E9adCY,217
11
11
  m3u8_analyzer/__version__.py,sha256=YP3yT87ZKrU3eARUUdQ_pg4xAXLGfBXjH4ZgEoZSq1I,25
12
12
  m3u8_analyzer/exeptions.py,sha256=fK6bU3YxNSbfsPmCp4yudUvmwy_g6dj2KwIkH0dW4LI,3672
13
13
  udemy_userAPI/__init__.py,sha256=BPle89xE_CMTKKe_Lw6jioYLgpH-q_Lpho2S-n1PIUA,206
14
- udemy_userAPI/__version__.py,sha256=av-dYivm9FvwaeydwdCTzwY6FYMnAjRiqjIqUkZFNAs,405
14
+ udemy_userAPI/__version__.py,sha256=FlHdJYlkLREupFjW2L1uKD5VxsxBYWG5W4dW5ylbmr8,405
15
15
  udemy_userAPI/api.py,sha256=dpwFtXewQmKwgG1IvzDFYZoEHNTwZbLIuv4WKgbqjOg,18817
16
- udemy_userAPI/authenticate.py,sha256=faIoOZdU5StQvUZfLEwZ3xfPba2FrFbc2HKEOeVY7w4,8246
17
- udemy_userAPI/bultins.py,sha256=XCXMe_5qKig_q5vbSFXBtx1vM87pE3UvEMKyUy6JCRo,12202
16
+ udemy_userAPI/authenticate.py,sha256=84frcOMfOzfCBfXDtoTa3POqkwWwuqgJ6h4ROF0TVAM,13850
17
+ udemy_userAPI/bultins.py,sha256=_-CM8Y-EuOEyg3kbNI2LKUONdCn2d1El1AmoNqFo0EU,12426
18
18
  udemy_userAPI/exeptions.py,sha256=nuZoAt4i-ctrW8zx9LZtejrngpFXDHOVE5cEXM4RtrY,508
19
19
  udemy_userAPI/sections.py,sha256=zPyDhvTIQCL0nbf7OJZG28Kax_iooILQ_hywUwvHoL8,4043
20
- udemy_userAPI/udemy.py,sha256=2UIy70wxWJAU7qZxvMDb3sHd1uQaffUXhib04ShIuI8,2124
20
+ udemy_userAPI/udemy.py,sha256=KMWMmid0zC9pUCULjLSAOK0P7yvCOtdShXpT6Q-fhro,2127
21
21
  udemy_userAPI/.cache/.udemy_userAPI,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  udemy_userAPI/mpd_analyzer/__init__.py,sha256=i3JVWyvcFLaj5kPmx8c1PgjsLht7OUIQQClD4yqYbo8,102
23
23
  udemy_userAPI/mpd_analyzer/bin.wvd,sha256=1rAJdCc120hQlX9qe5KUS628eY2ZHYxQSmyhGNefSzo,2956
24
24
  udemy_userAPI/mpd_analyzer/mpd_parser.py,sha256=_vw1feJXDjw5fQLOmA5-H3UklX_30Pbl__HtDUqvp3c,17283
25
- udemy_userAPI-0.2.5.dist-info/LICENSE,sha256=l4jdKYt8gSdDFOGr09vCKnMn_Im55XIcQKqTDEtFfNs,1095
26
- udemy_userAPI-0.2.5.dist-info/METADATA,sha256=jx7z2l2U-YUTezhxrBjEgC6Ohq01YsqjjRXJ5jPYoGM,1394
27
- udemy_userAPI-0.2.5.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
28
- udemy_userAPI-0.2.5.dist-info/top_level.txt,sha256=SrUygl6WJt34qYigm-FiGqKCs2M4S_f-aU2i6oJ3nDM,65
29
- udemy_userAPI-0.2.5.dist-info/RECORD,,
25
+ udemy_userAPI-0.2.7.dist-info/LICENSE,sha256=l4jdKYt8gSdDFOGr09vCKnMn_Im55XIcQKqTDEtFfNs,1095
26
+ udemy_userAPI-0.2.7.dist-info/METADATA,sha256=V833TKA7jMmFMj5aeLGOeSt6GSmpgeUzl2dS0Q33NJw,1394
27
+ udemy_userAPI-0.2.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
28
+ udemy_userAPI-0.2.7.dist-info/top_level.txt,sha256=ijTINaSDRKhdahY_X7dmSRFTxBIwQErWv9ATCG55mog,14
29
+ udemy_userAPI-0.2.7.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ udemy_userAPI
@@ -1,4 +0,0 @@
1
- animation_consoles
2
- ffmpeg_for_python
3
- m3u8_analyzer
4
- udemy_userAPI