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.
@@ -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)