udemy-userAPI 0.3.2__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.
- animation_consoles/__init__.py +1 -0
- animation_consoles/animation.py +64 -0
- ffmpeg_for_python/__config__.py +118 -0
- ffmpeg_for_python/__init__.py +8 -0
- ffmpeg_for_python/__utils.py +78 -0
- ffmpeg_for_python/__version__.py +6 -0
- ffmpeg_for_python/exeptions.py +91 -0
- ffmpeg_for_python/ffmpeg.py +203 -0
- m3u8_analyzer/M3u8Analyzer.py +807 -0
- m3u8_analyzer/__init__.py +7 -0
- m3u8_analyzer/__version__.py +1 -0
- m3u8_analyzer/exeptions.py +82 -0
- udemy_userAPI/.cache/.udemy_userAPI +0 -0
- udemy_userAPI/__init__.py +7 -0
- udemy_userAPI/__version__.py +6 -0
- udemy_userAPI/api.py +691 -0
- udemy_userAPI/authenticate.py +311 -0
- udemy_userAPI/bultins.py +495 -0
- udemy_userAPI/exeptions.py +22 -0
- udemy_userAPI/mpd_analyzer/__init__.py +3 -0
- udemy_userAPI/mpd_analyzer/bin.wvd +0 -0
- udemy_userAPI/mpd_analyzer/mpd_parser.py +224 -0
- udemy_userAPI/sections.py +117 -0
- udemy_userAPI/udemy.py +93 -0
- udemy_userAPI-0.3.2.dist-info/LICENSE +21 -0
- udemy_userAPI-0.3.2.dist-info/METADATA +34 -0
- udemy_userAPI-0.3.2.dist-info/RECORD +29 -0
- udemy_userAPI-0.3.2.dist-info/WHEEL +5 -0
- udemy_userAPI-0.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,311 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
import pickle
|
4
|
+
import traceback
|
5
|
+
import requests
|
6
|
+
from .exeptions import UnhandledExceptions, UdemyUserApiExceptions, LoginException, Upstreamconnecterror
|
7
|
+
import cloudscraper
|
8
|
+
|
9
|
+
DEBUG = False
|
10
|
+
|
11
|
+
|
12
|
+
class UdemyAuth:
|
13
|
+
def __init__(self):
|
14
|
+
"""
|
15
|
+
Autenticação na plataforma Udemy de maneira segura.
|
16
|
+
Atenção ao limite de logins. Recomendo que após logar não use novamente o método login,
|
17
|
+
use apenas o verificador de login para evitar bloqueios temporários.
|
18
|
+
"""
|
19
|
+
self.__cookie_dict = {}
|
20
|
+
current_directory = os.path.dirname(__file__)
|
21
|
+
cache = '.cache'
|
22
|
+
cache_dir = os.path.join(current_directory, cache)
|
23
|
+
os.makedirs(cache_dir, exist_ok=True)
|
24
|
+
self.__user_dir = os.path.join(cache_dir)
|
25
|
+
file_name = '.udemy_userAPI'
|
26
|
+
file_credenntials = '.udemy_Credentials'
|
27
|
+
self.__file_path = os.path.join(self.__user_dir, file_name)
|
28
|
+
self.__credentials_path = os.path.join(self.__user_dir, file_credenntials)
|
29
|
+
|
30
|
+
def verif_login(self) -> bool:
|
31
|
+
"""
|
32
|
+
Verifica se o usuário está logado.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
bool: True se o usuário estiver logado, False caso contrário.
|
36
|
+
"""
|
37
|
+
|
38
|
+
def verif_config():
|
39
|
+
"""
|
40
|
+
Verifica se o arquivo .userLogin existe e carrega cookies se existir.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
str: Cookies em formato de string ou False se não existir.
|
44
|
+
"""
|
45
|
+
try:
|
46
|
+
with open(fr'{self.__file_path}', 'rb') as f:
|
47
|
+
cookies = pickle.load(f)
|
48
|
+
cookies_dict = {cookie.name: cookie.value for cookie in cookies}
|
49
|
+
cookies_str = "; ".join([f"{key}={value}" for key, value in cookies_dict.items()])
|
50
|
+
return cookies_str
|
51
|
+
except Exception as e:
|
52
|
+
if DEBUG:
|
53
|
+
e = traceback.format_exc()
|
54
|
+
raise LoginException(e)
|
55
|
+
return False
|
56
|
+
|
57
|
+
log = verif_config()
|
58
|
+
|
59
|
+
if log:
|
60
|
+
cookies_de_secao = log
|
61
|
+
headers = {
|
62
|
+
"accept": "*/*",
|
63
|
+
"accept-language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
|
64
|
+
"cache-control": "no-cache",
|
65
|
+
"Content-Type": "text/plain",
|
66
|
+
"pragma": "no-cache",
|
67
|
+
"sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"",
|
68
|
+
"sec-ch-ua-mobile": "?0",
|
69
|
+
"sec-ch-ua-platform": "\"Windows\"",
|
70
|
+
"sec-fetch-dest": "empty",
|
71
|
+
"sec-fetch-mode": "cors",
|
72
|
+
"sec-fetch-site": "cross-site",
|
73
|
+
"Cookie": cookies_de_secao,
|
74
|
+
"Referer": "https://www.udemy.com/"
|
75
|
+
}
|
76
|
+
|
77
|
+
try:
|
78
|
+
url = 'https://www.udemy.com/api-2.0/contexts/me/?header=true'
|
79
|
+
resp = requests.get(url=url, headers=headers)
|
80
|
+
if resp.status_code == 200:
|
81
|
+
convert = json.loads(resp.text)
|
82
|
+
isLoggedIn = convert.get('header', {}).get('isLoggedIn', False)
|
83
|
+
if isLoggedIn:
|
84
|
+
if isLoggedIn is True:
|
85
|
+
return True
|
86
|
+
else:
|
87
|
+
return False
|
88
|
+
else:
|
89
|
+
return False
|
90
|
+
else:
|
91
|
+
if (('upstream connect error or disconnect/reset before headers.'
|
92
|
+
' reset reason: remote connection'
|
93
|
+
' failure, transport failure reason:'
|
94
|
+
' delayed connect error: 111')
|
95
|
+
in resp.text):
|
96
|
+
raise Upstreamconnecterror(message='Erro no servidor remoto!')
|
97
|
+
raise LoginException(f"Erro Ao obter login atualize a lib! -> {resp.text}")
|
98
|
+
except requests.ConnectionError as e:
|
99
|
+
raise UdemyUserApiExceptions(f"Erro de conexão: {e}")
|
100
|
+
except requests.Timeout as e:
|
101
|
+
raise UdemyUserApiExceptions(f"Tempo de requisição excedido: {e}")
|
102
|
+
except requests.TooManyRedirects as e:
|
103
|
+
raise UdemyUserApiExceptions(f"Limite de redirecionamentos excedido: {e}")
|
104
|
+
except requests.HTTPError as e:
|
105
|
+
raise UdemyUserApiExceptions(f"Erro HTTP: {e}")
|
106
|
+
except Exception as e:
|
107
|
+
raise UnhandledExceptions(f"Unhandled-ERROR: {e}")
|
108
|
+
else:
|
109
|
+
return False
|
110
|
+
|
111
|
+
def login(self, email: str, password: str, locale: str = 'pt_BR'):
|
112
|
+
"""
|
113
|
+
Efetua login na Udemy.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
email (str): Email do usuário.
|
117
|
+
password (str): Senha do usuário.
|
118
|
+
locale (str): Localidade. Padrão é 'pt_BR'.
|
119
|
+
"""
|
120
|
+
try:
|
121
|
+
if self.verif_login():
|
122
|
+
raise UserWarning("Atenção, você já possui uma sessão válida!")
|
123
|
+
s = cloudscraper.create_scraper()
|
124
|
+
r = s.get(
|
125
|
+
"https://www.udemy.com/join/signup-popup/",
|
126
|
+
headers={"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"},
|
127
|
+
)
|
128
|
+
csrf_token = r.cookies["csrftoken"]
|
129
|
+
data = {
|
130
|
+
"csrfmiddlewaretoken": csrf_token,
|
131
|
+
"locale": locale,
|
132
|
+
"email": email,
|
133
|
+
"password": password,
|
134
|
+
}
|
135
|
+
s.cookies.update(r.cookies)
|
136
|
+
s.headers.update(
|
137
|
+
{
|
138
|
+
"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
|
139
|
+
"Accept": "application/json, text/plain, */*",
|
140
|
+
"Accept-Language": "en-GB,en;q=0.5",
|
141
|
+
"Referer": "https://www.udemy.com/join/login-popup/?locale=en_US&response_type="
|
142
|
+
"html&next=https%3A%2F"
|
143
|
+
"%2Fwww.udemy.com%2F",
|
144
|
+
"Origin": "https://www.udemy.com",
|
145
|
+
"DNT": "1",
|
146
|
+
"Connection": "keep-alive",
|
147
|
+
"Sec-Fetch-Dest": "empty",
|
148
|
+
"Sec-Fetch-Mode": "cors",
|
149
|
+
"Sec-Fetch-Site": "same-origin",
|
150
|
+
"Pragma": "no-cache",
|
151
|
+
"Cache-Control": "no-cache",
|
152
|
+
}
|
153
|
+
)
|
154
|
+
|
155
|
+
# Tenta fazer login com as credenciais fornecidas
|
156
|
+
r = s.post(
|
157
|
+
"https://www.udemy.com/join/login-popup/?response_type=json",
|
158
|
+
data=data,
|
159
|
+
allow_redirects=False,
|
160
|
+
)
|
161
|
+
|
162
|
+
# Verifica a resposta para determinar se o login foi bem-sucedido
|
163
|
+
if "returnUrl" in r.text:
|
164
|
+
self._save_cookies(s.cookies)
|
165
|
+
else:
|
166
|
+
login_error = r.json().get("error", {}).get("data", {}).get("formErrors", [])[0]
|
167
|
+
if login_error[0] == "Y":
|
168
|
+
raise LoginException("Você excedeu o número máximo de solicitações por hora.")
|
169
|
+
elif login_error[0] == "T":
|
170
|
+
raise LoginException("Email ou senha incorretos")
|
171
|
+
else:
|
172
|
+
raise UnhandledExceptions(login_error)
|
173
|
+
except Exception as e:
|
174
|
+
if DEBUG:
|
175
|
+
e = traceback.format_exc()
|
176
|
+
raise LoginException(e)
|
177
|
+
|
178
|
+
def _save_cookies(self, cookies):
|
179
|
+
try:
|
180
|
+
with open(fr'{self.__file_path}', 'wb') as f:
|
181
|
+
pickle.dump(cookies, f)
|
182
|
+
except Exception as e:
|
183
|
+
raise LoginException(e)
|
184
|
+
|
185
|
+
def _load_cookies(self) -> str:
|
186
|
+
"""Carrega cookies e retorna-os em uma string formatada"""
|
187
|
+
try:
|
188
|
+
file = os.path.join(self.__file_path)
|
189
|
+
if os.path.exists(file) and os.path.getsize(file) > 0: # Verifica se o arquivo existe e não está vazio
|
190
|
+
with open(file, 'rb') as f:
|
191
|
+
cookies = pickle.load(f)
|
192
|
+
# Converte cookies em formato de string
|
193
|
+
cookies_dict = {cookie.name: cookie.value for cookie in cookies}
|
194
|
+
cookies_str = "; ".join([f"{key}={value}" for key, value in cookies_dict.items()])
|
195
|
+
return cookies_str
|
196
|
+
else:
|
197
|
+
return "" # Retorna uma string vazia se o arquivo não existir ou estiver vazio
|
198
|
+
except (EOFError, pickle.UnpicklingError): # Trata arquivos vazios ou corrompidos
|
199
|
+
return "" # Retorna uma string vazia
|
200
|
+
except Exception as e:
|
201
|
+
if DEBUG:
|
202
|
+
e = traceback.format_exc()
|
203
|
+
raise LoginException(f"Erro ao carregar cookies: {e}")
|
204
|
+
|
205
|
+
def remove_cookies(self):
|
206
|
+
if os.path.exists(self.__file_path):
|
207
|
+
with open(self.__file_path, 'wb') as f:
|
208
|
+
f.write(b'')
|
209
|
+
|
210
|
+
def login_passwordless(self, email: str, locale: str = 'pt-BR'):
|
211
|
+
"""
|
212
|
+
Realiza login na Udemy usando autenticação de dois fatores (2FA).
|
213
|
+
|
214
|
+
Este método utiliza o fluxo de autenticação OAuth da Udemy para enviar um
|
215
|
+
código de verificação por e-mail ao usuário. Após inserir o código recebido,
|
216
|
+
o login é concluído.
|
217
|
+
|
218
|
+
Args:
|
219
|
+
email (str): Email do usuário.
|
220
|
+
locale (str): Localização do usuário (recomendado para receber mensagens no idioma local).
|
221
|
+
|
222
|
+
Raises:
|
223
|
+
LoginException: Em caso de falha no processo de login.
|
224
|
+
"""
|
225
|
+
from .api import J
|
226
|
+
try:
|
227
|
+
if self.verif_login():
|
228
|
+
raise UserWarning("Atenção, você já possui uma sessão válida!")
|
229
|
+
# Inicializa uma sessão com proteção contra Cloudflare
|
230
|
+
session = cloudscraper.create_scraper()
|
231
|
+
|
232
|
+
# Requisita a página de inscrição para obter o token CSRF
|
233
|
+
signup_url = "https://www.udemy.com/join/signup-popup/"
|
234
|
+
headers = {"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"}
|
235
|
+
response = session.get(signup_url, headers=headers)
|
236
|
+
|
237
|
+
# Obtém o token CSRF dos cookies retornados
|
238
|
+
csrf_token = response.cookies.get("csrftoken")
|
239
|
+
if not csrf_token:
|
240
|
+
raise LoginException("Não foi possível obter o token CSRF.")
|
241
|
+
|
242
|
+
# Prepara os dados do login
|
243
|
+
data = {"email": email, "fullname": ""}
|
244
|
+
|
245
|
+
# Atualiza os cookies e cabeçalhos da sessão
|
246
|
+
session.cookies.update(response.cookies)
|
247
|
+
session.headers.update({
|
248
|
+
"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
|
249
|
+
"Accept": "application/json, text/plain, */*",
|
250
|
+
"Accept-Language": locale,
|
251
|
+
"Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale.replace('-', '_')}&next="
|
252
|
+
f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html",
|
253
|
+
"Origin": "https://www.udemy.com",
|
254
|
+
"DNT": "1",
|
255
|
+
"Connection": "keep-alive",
|
256
|
+
"Sec-Fetch-Dest": "empty",
|
257
|
+
"Sec-Fetch-Mode": "cors",
|
258
|
+
"Sec-Fetch-Site": "same-origin",
|
259
|
+
"Pragma": "no-cache",
|
260
|
+
"Cache-Control": "no-cache",
|
261
|
+
})
|
262
|
+
|
263
|
+
# Faz a requisição para iniciar o login
|
264
|
+
login_url = "https://www.udemy.com/api-2.0/auth/code-generation/login/4.0/"
|
265
|
+
response = session.post(login_url, data=data, allow_redirects=False)
|
266
|
+
if 'error_message' in response.text:
|
267
|
+
erro_data: dict = response.json()
|
268
|
+
error_message = erro_data.get('error_message', {})
|
269
|
+
raise LoginException(error_message)
|
270
|
+
for attempt in range(3):
|
271
|
+
# Solicita o código OTP ao usuário
|
272
|
+
otp = input("Digite o código de 6 dígitos enviado ao seu e-mail: ")
|
273
|
+
# Realiza o login com o código OTP
|
274
|
+
otp_login_url = "https://www.udemy.com/api-2.0/auth/udemy-passwordless/login/4.0/"
|
275
|
+
otp_data = {
|
276
|
+
"email": email,
|
277
|
+
"fullname": "",
|
278
|
+
"otp": otp,
|
279
|
+
"subscribeToEmails": "false",
|
280
|
+
"upow": J(email, 'login')
|
281
|
+
}
|
282
|
+
session.headers.update({
|
283
|
+
"Referer": f"https://www.udemy.com/join/passwordless-auth/?locale={locale}&next="
|
284
|
+
f"https%3A%2F%2Fwww.udemy.com%2Fmobile%2Fipad%2F&response_type=html"
|
285
|
+
})
|
286
|
+
response = session.post(otp_login_url, otp_data, allow_redirects=False)
|
287
|
+
# Verifica se o login foi bem-sucedido
|
288
|
+
if response.status_code == 200:
|
289
|
+
self._save_cookies(session.cookies)
|
290
|
+
else:
|
291
|
+
if 'error_message' in response.text:
|
292
|
+
erro_data: dict = response.json()
|
293
|
+
error_message = erro_data.get('error_message', {})
|
294
|
+
error_code = erro_data.get('error_code', {})
|
295
|
+
if error_code == '1538':
|
296
|
+
raise LoginException(error_message)
|
297
|
+
elif error_code == '2550':
|
298
|
+
print(error_message)
|
299
|
+
continue
|
300
|
+
elif error_code == '1330':
|
301
|
+
raise LoginException(error_message)
|
302
|
+
elif error_code == '1149':
|
303
|
+
raise LoginException(f"Erro interno ao enviar os dados veja os detalhes: '{error_message}'")
|
304
|
+
raise LoginException(response.text)
|
305
|
+
break
|
306
|
+
except Exception as e:
|
307
|
+
if DEBUG:
|
308
|
+
error_details = traceback.format_exc()
|
309
|
+
else:
|
310
|
+
error_details = str(e)
|
311
|
+
raise LoginException(error_details)
|