udemy-userAPI 0.3.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)