udemy-userAPI 0.2.5__py3-none-any.whl → 0.2.6__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- udemy_userAPI/__version__.py +1 -1
- udemy_userAPI/authenticate.py +91 -7
- udemy_userAPI/bultins.py +20 -12
- udemy_userAPI/udemy.py +7 -6
- {udemy_userAPI-0.2.5.dist-info → udemy_userAPI-0.2.6.dist-info}/METADATA +2 -2
- {udemy_userAPI-0.2.5.dist-info → udemy_userAPI-0.2.6.dist-info}/RECORD +9 -9
- udemy_userAPI-0.2.6.dist-info/top_level.txt +1 -0
- udemy_userAPI-0.2.5.dist-info/top_level.txt +0 -4
- {udemy_userAPI-0.2.5.dist-info → udemy_userAPI-0.2.6.dist-info}/LICENSE +0 -0
- {udemy_userAPI-0.2.5.dist-info → udemy_userAPI-0.2.6.dist-info}/WHEEL +0 -0
udemy_userAPI/__version__.py
CHANGED
udemy_userAPI/authenticate.py
CHANGED
@@ -2,7 +2,7 @@ import json
|
|
2
2
|
import os
|
3
3
|
import pickle
|
4
4
|
import traceback
|
5
|
-
|
5
|
+
from datetime import datetime
|
6
6
|
import requests
|
7
7
|
from .exeptions import UnhandledExceptions, UdemyUserApiExceptions, LoginException
|
8
8
|
import cloudscraper
|
@@ -13,7 +13,7 @@ DEBUG = False
|
|
13
13
|
class UdemyAuth:
|
14
14
|
def __init__(self):
|
15
15
|
"""Autenticação na plataforma udemy de maneira segura, atencao ao limite de logins,recomendo que apos logar
|
16
|
-
nao use
|
16
|
+
nao use novamente o metodo login use apenas o verifcador de login para evitar bloqueios temporários..."""
|
17
17
|
self.__cookie_dict = {}
|
18
18
|
# Diretório do arquivo atual
|
19
19
|
current_directory = os.path.dirname(__file__)
|
@@ -142,7 +142,7 @@ class UdemyAuth:
|
|
142
142
|
|
143
143
|
# Verifica a resposta para determinar se o login foi bem-sucedido
|
144
144
|
if "returnUrl" in r.text:
|
145
|
-
self.
|
145
|
+
self._save_cookies(s.cookies)
|
146
146
|
else:
|
147
147
|
login_error = r.json().get("error", {}).get("data", {}).get("formErrors", [])[0]
|
148
148
|
if login_error[0] == "Y":
|
@@ -151,14 +151,12 @@ class UdemyAuth:
|
|
151
151
|
raise LoginException("Email ou senha incorretos")
|
152
152
|
else:
|
153
153
|
raise UnhandledExceptions(login_error)
|
154
|
-
|
155
|
-
return s
|
156
154
|
except Exception as e:
|
157
155
|
if DEBUG:
|
158
156
|
e = traceback.format_exc()
|
159
157
|
raise LoginException(e)
|
160
158
|
|
161
|
-
def
|
159
|
+
def _save_cookies(self, cookies):
|
162
160
|
try:
|
163
161
|
with open(fr'{self.__file_path}', 'wb') as f:
|
164
162
|
pickle.dump(cookies, f)
|
@@ -187,5 +185,91 @@ class UdemyAuth:
|
|
187
185
|
raise LoginException(f"Erro ao carregar cookies: {e}")
|
188
186
|
|
189
187
|
def remove_cookies(self):
|
188
|
+
"""remove os cookies salvos"""
|
190
189
|
if os.path.exists(self.__file_path):
|
191
|
-
|
190
|
+
with open(self.__file_path, 'wb') as f:
|
191
|
+
f.write(b'')
|
192
|
+
|
193
|
+
def login_passwordless(self, email: str, locale: str = 'pt-BR'):
|
194
|
+
"""
|
195
|
+
Realiza login na Udemy usando autenticação de dois fatores (2FA).
|
196
|
+
|
197
|
+
Este método utiliza o fluxo de autenticação OAuth da Udemy para enviar um
|
198
|
+
código de verificação por e-mail ao usuário. Após inserir o código recebido,
|
199
|
+
o login é concluído.
|
200
|
+
|
201
|
+
:param email: Email do usuário.
|
202
|
+
:param locale: Localização do usuário (recomendado para receber mensagens no idioma local).
|
203
|
+
:raises LoginException: Em caso de falha no processo de login.
|
204
|
+
"""
|
205
|
+
try:
|
206
|
+
# Inicializa uma sessão com proteção contra Cloudflare
|
207
|
+
session = cloudscraper.create_scraper()
|
208
|
+
|
209
|
+
# Requisita a página de inscrição para obter o token CSRF
|
210
|
+
signup_url = "https://www.udemy.com/join/signup-popup/"
|
211
|
+
headers = {"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"}
|
212
|
+
response = session.get(signup_url, headers=headers)
|
213
|
+
|
214
|
+
# Obtém o token CSRF dos cookies retornados
|
215
|
+
csrf_token = response.cookies.get("csrftoken")
|
216
|
+
if not csrf_token:
|
217
|
+
raise LoginException("Não foi possível obter o token CSRF.")
|
218
|
+
|
219
|
+
# Prepara os dados do login
|
220
|
+
data = {"email": email, "fullname": ""}
|
221
|
+
|
222
|
+
# Atualiza os cookies e cabeçalhos da sessão
|
223
|
+
session.cookies.update(response.cookies)
|
224
|
+
session.headers.update({
|
225
|
+
"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
|
226
|
+
"Accept": "application/json, text/plain, */*",
|
227
|
+
"Accept-Language": locale,
|
228
|
+
"Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale.replace('-', '_')}&next="
|
229
|
+
f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html",
|
230
|
+
"Origin": "https://www.udemy.com",
|
231
|
+
"DNT": "1",
|
232
|
+
"Connection": "keep-alive",
|
233
|
+
"Sec-Fetch-Dest": "empty",
|
234
|
+
"Sec-Fetch-Mode": "cors",
|
235
|
+
"Sec-Fetch-Site": "same-origin",
|
236
|
+
"Pragma": "no-cache",
|
237
|
+
"Cache-Control": "no-cache",
|
238
|
+
})
|
239
|
+
|
240
|
+
# Faz a requisição para iniciar o login
|
241
|
+
login_url = "https://www.udemy.com/api-2.0/auth/code-generation/login/4.0/"
|
242
|
+
response = session.post(login_url, data=data, allow_redirects=False)
|
243
|
+
if 'error_message' in response.text:
|
244
|
+
raise LoginException(f"Erro no login inicial: {response.text}")
|
245
|
+
|
246
|
+
# Solicita o código OTP ao usuário
|
247
|
+
otp = input("Digite o código de 6 dígitos enviado ao seu e-mail: ")
|
248
|
+
upow = datetime.now().strftime("%Y-%m-%d")
|
249
|
+
# Realiza o login com o código OTP
|
250
|
+
otp_login_url = "https://www.udemy.com/api-2.0/auth/udemy-passwordless/login/4.0/"
|
251
|
+
otp_data = {
|
252
|
+
"email": email,
|
253
|
+
"fullname": "",
|
254
|
+
"otp": otp,
|
255
|
+
"subscribeToEmails": "false",
|
256
|
+
"upow": f"{upow}XBY",
|
257
|
+
}
|
258
|
+
session.headers.update({
|
259
|
+
"Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale}&next="
|
260
|
+
f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html"
|
261
|
+
})
|
262
|
+
response = session.post(otp_login_url, otp_data, allow_redirects=False)
|
263
|
+
|
264
|
+
if response.status_code == 200:
|
265
|
+
self._save_cookies(session.cookies)
|
266
|
+
else:
|
267
|
+
raise LoginException(f"Falha no login OTP: {response.text}")
|
268
|
+
except Exception as e:
|
269
|
+
if DEBUG:
|
270
|
+
error_details = traceback.format_exc()
|
271
|
+
else:
|
272
|
+
error_details = str(e)
|
273
|
+
raise LoginException(error_details)
|
274
|
+
|
275
|
+
|
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.
|
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,
|
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
|
-
|
63
|
-
|
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
|
-
|
23
|
-
|
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
|
-
|
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.
|
3
|
+
Version: 0.2.6
|
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
|
-

|
23
23
|

|
24
24
|
[](https://apoia.se/paulocesar-dev404)
|
25
25
|
[](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=
|
14
|
+
udemy_userAPI/__version__.py,sha256=rzNgClR2jOV4dqJpZ-J9iW5J_qFV_BiiYOYrBzRWseQ,405
|
15
15
|
udemy_userAPI/api.py,sha256=dpwFtXewQmKwgG1IvzDFYZoEHNTwZbLIuv4WKgbqjOg,18817
|
16
|
-
udemy_userAPI/authenticate.py,sha256=
|
17
|
-
udemy_userAPI/bultins.py,sha256=
|
16
|
+
udemy_userAPI/authenticate.py,sha256=ywAM-85c0UnpoTc-eiEysYIEOY5r4SytbngwuV86wgY,12292
|
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=
|
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.
|
26
|
-
udemy_userAPI-0.2.
|
27
|
-
udemy_userAPI-0.2.
|
28
|
-
udemy_userAPI-0.2.
|
29
|
-
udemy_userAPI-0.2.
|
25
|
+
udemy_userAPI-0.2.6.dist-info/LICENSE,sha256=l4jdKYt8gSdDFOGr09vCKnMn_Im55XIcQKqTDEtFfNs,1095
|
26
|
+
udemy_userAPI-0.2.6.dist-info/METADATA,sha256=klDgLa9mjUKStI8BUjgEWHA8azJKyDut_bJvHpoLWz8,1394
|
27
|
+
udemy_userAPI-0.2.6.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
28
|
+
udemy_userAPI-0.2.6.dist-info/top_level.txt,sha256=ijTINaSDRKhdahY_X7dmSRFTxBIwQErWv9ATCG55mog,14
|
29
|
+
udemy_userAPI-0.2.6.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
udemy_userAPI
|
File without changes
|
File without changes
|