udemy-userAPI 0.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 PauloCesar-dev404
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include README_PYPI.md
2
+
3
+
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.1
2
+ Name: udemy_userAPI
3
+ Version: 0.1
4
+ Summary: Obtenha detalhes de cursos que o usuário esteja inscrito da plataforma Udemy,usando o EndPoint de
5
+ Author: PauloCesar-dev404
6
+ Author-email: paulocesar0073dev404@gmail.com
7
+ License: MIT
8
+ Project-URL: Código Fonte, https://raw.githubusercontent.com/PauloCesar-dev404/udemy_userAPI/main/udemy_userAPI-0.1.tar.gz
9
+ Project-URL: lib, https://raw.githubusercontent.com/PauloCesar-dev404/udemy_userAPI/main/udemy_userAPI-0.1-py3-none-any.whl
10
+ Project-URL: GitHub, https://github.com/PauloCesar-dev404/udemy_userAPI
11
+ Project-URL: Bugs/Melhorias, https://github.com/PauloCesar-dev404/udemy_userAPI/issues
12
+ Project-URL: Documentação, https://github.com/PauloCesar-dev404/udemy_userAPI/wiki
13
+ Keywords: udemy,udemy python,pyudemy,udemy_userAPI,udemy api
14
+ Platform: any
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests
18
+ Requires-Dist: cloudscraper
19
+
20
+ # udemy-userAPI
21
+
22
+
23
+ ![Versão](https://img.shields.io/badge/version-0.1-orange)
24
+ ![Licença](https://img.shields.io/badge/license-MIT-orange)
25
+ [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://apoia.se/paulocesar-dev404)
26
+ [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
27
+
28
+
29
+ Obtenha detalhes de cursos da plataforma udemy com a api de usuário,usando esta lib
30
+ ---
31
+ - [x] Obter cursos inscritos(acesso por plano ou cursos free)
32
+ - [x] Obter detalhes de Aulas
33
+ - [x] Obter detalhes de um Curso
34
+
@@ -0,0 +1,25 @@
1
+ <div align="center">
2
+ <img src="assets/udemy_userAPI-logo.png" alt="udemy_userAPI-logo" width="200"/>
3
+
4
+
5
+ ![Versão](https://img.shields.io/badge/version-0.1-orange)
6
+ ![Licença](https://img.shields.io/badge/license-MIT-orange)
7
+ [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://apoia.se/paulocesar-dev404)
8
+ [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
9
+
10
+
11
+ <i>Obtenha detalhes completos de cursos cujo o usuário esteja inscrito ,da plataforma [Udemy](https://www.udemy.com/) com esta biblioteca</i>
12
+
13
+ ---
14
+ </div>
15
+
16
+
17
+ ### Funcionalidades
18
+ - [x] Obter cursos inscritos (cursos com acesso por plano ou comprados ou cursos Grátis)
19
+ - [x] Obter detalhes de Aulas
20
+ - [x] Obter detalhes de um Curso
21
+
22
+ Se tiver dúvidas ou sugestões, abra uma [issue aqui](https://github.com/PauloCesar-dev404/udemy_userAPI/issues).
23
+
24
+ ---
25
+
@@ -0,0 +1,15 @@
1
+ # udemy-userAPI
2
+
3
+
4
+ ![Versão](https://img.shields.io/badge/version-0.1-orange)
5
+ ![Licença](https://img.shields.io/badge/license-MIT-orange)
6
+ [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://apoia.se/paulocesar-dev404)
7
+ [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
8
+
9
+
10
+ Obtenha detalhes de cursos da plataforma udemy com a api de usuário,usando esta lib
11
+ ---
12
+ - [x] Obter cursos inscritos(acesso por plano ou cursos free)
13
+ - [x] Obter detalhes de Aulas
14
+ - [x] Obter detalhes de um Curso
15
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ from udemy_userAPI.__version__ import __version__, __autor__, __repo__, __source__, __lib__, __lib_name__, \
2
+ __description__
3
+ from setuptools import setup, find_packages
4
+
5
+ # Lê o conteúdo do README.md
6
+ with open('README_PYPI.md', 'r', encoding='utf-8') as file:
7
+ long_description = file.read()
8
+
9
+ setup(
10
+ name=__lib_name__,
11
+ version=__version__,
12
+ description=__description__,
13
+ long_description=long_description,
14
+ long_description_content_type="text/markdown",
15
+ author=__autor__,
16
+ author_email="paulocesar0073dev404@gmail.com",
17
+ license="MIT",
18
+ keywords=["udemy", "udemy python", "pyudemy","udemy_userAPI","udemy api"],
19
+ install_requires=[
20
+ 'requests',
21
+ 'cloudscraper'
22
+ ],
23
+ packages=find_packages(),
24
+ zip_safe=False,
25
+ include_package_data=True,
26
+ platforms=["any"],
27
+ project_urls={
28
+ "Código Fonte": __source__,
29
+ "lib": __lib__,
30
+ 'GitHub': __repo__,
31
+ "Bugs/Melhorias": f"{__repo__}/issues",
32
+ "Documentação": f"{__repo__}/wiki",
33
+ }
34
+ )
@@ -0,0 +1,7 @@
1
+ from .udemy import Udemy
2
+ from .authenticate import UdemyAuth
3
+ from .exeptions import UdemyUserApiExceptions,LoginException
4
+
5
+
6
+ __all_ = ['Udemy','UdemyAuth','UdemyUserApiExceptions','LoginException']
7
+
@@ -0,0 +1,11 @@
1
+ __version__ = '0.1'
2
+ __lib_name__ = 'udemy_userAPI'
3
+ __autor__ = 'PauloCesar-dev404'
4
+ __repo__ = f'https://github.com/PauloCesar-dev404/{__lib_name__}'
5
+ __lib__ = (f'https://raw.githubusercontent.com/PauloCesar-dev404/{__lib_name__}/main/{__lib_name__}-{__version__}-py3'
6
+ f'-none-any.whl')
7
+ __source__ = (f'https://raw.githubusercontent.com/PauloCesar-dev404/{__lib_name__}/main/{__lib_name__}-{__version__}'
8
+ f'.tar.gz')
9
+ __description__ = """Obtenha detalhes de cursos que o usuário esteja inscrito da plataforma Udemy,usando o EndPoint de
10
+ usuário o mesmo que o navegador utiliza para
11
+ acessar e redenrizar os cursos."""
@@ -0,0 +1,259 @@
1
+ import json
2
+ import requests
3
+ from .exeptions import UdemyUserApiExceptions, UnhandledExceptions
4
+ from .authenticate import UdemyAuth
5
+
6
+ AUTH = UdemyAuth()
7
+ COOKIES = AUTH.load_cookies
8
+
9
+ HEADERS_USER = {
10
+ "accept": "*/*",
11
+ "accept-language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
12
+ "cache-control": "no-cache",
13
+ "Content-Type": "text/plain",
14
+ "pragma": "no-cache",
15
+ "sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"",
16
+ "sec-ch-ua-mobile": "?0",
17
+ "sec-ch-ua-platform": "\"Windows\"",
18
+ "sec-fetch-dest": "empty",
19
+ "sec-fetch-mode": "cors",
20
+ "sec-fetch-site": "cross-site",
21
+ "Cookie": COOKIES,
22
+ "Referer": "https://www.udemy.com/"}
23
+ HEADERS_octet_stream = {
24
+ 'authority': 'www.udemy.com',
25
+ 'pragma': 'no-cache',
26
+ 'cache-control': 'no-cache',
27
+ 'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"',
28
+ 'accept': 'application/json, text/plain, */*',
29
+ "Cookie": COOKIES,
30
+ 'dnt': '1',
31
+ 'content-type': 'application/octet-stream',
32
+ 'sec-ch-ua-mobile': '?0',
33
+ 'sec-ch-ua-platform': '"Windows"',
34
+ 'origin': 'https://www.udemy.com',
35
+ 'sec-fetch-site': 'same-origin',
36
+ 'sec-fetch-mode': 'cors',
37
+ 'sec-fetch-dest': 'empty',
38
+ 'accept-language': 'en-US,en;q=0.9',
39
+ }
40
+
41
+
42
+ def parser_chapers(results):
43
+ """
44
+ :param results:
45
+ :param tip: chaper,videos
46
+ :return:
47
+ """
48
+ if not results:
49
+ raise UdemyUserApiExceptions("Não foi possível obter detalhes do curso!")
50
+ results = results.get('results', None)
51
+ if not results:
52
+ raise UdemyUserApiExceptions("Não foi possível obter detalhes do curso!")
53
+ chapters_dict = {} # Dicionário para armazenar os capítulos e seus vídeos correspondentes
54
+
55
+ # Primeiro, construímos um dicionário de capítulos
56
+ current_chapter = None
57
+ for dictionary in results:
58
+ _class = dictionary.get('_class')
59
+
60
+ if _class == 'chapter':
61
+ chapter_index = dictionary.get('object_index')
62
+ current_chapter = {
63
+ 'title_chapter': dictionary.get('title'),
64
+ 'videos_in_chapter': []
65
+ }
66
+ chapters_dict[f"chapter_{chapter_index}"] = current_chapter
67
+ elif _class == 'lecture' and current_chapter is not None:
68
+ asset = dictionary.get('asset')
69
+ if asset:
70
+ video_title = asset.get('title', None)
71
+ if not video_title:
72
+ video_title = 'Files'
73
+ current_chapter['videos_in_chapter'].append({
74
+ 'video_title': video_title,
75
+ 'title_lecture': dictionary.get('title'),
76
+ 'id_lecture': dictionary.get('id'),
77
+ 'id_asset': asset.get('id')
78
+ })
79
+
80
+ return chapters_dict
81
+
82
+
83
+ def get_links(course_id: int, id_lecture: int):
84
+ """
85
+ :param course_id: id do curso
86
+ :param id_lecture: id da aula
87
+ :return: dict
88
+ """
89
+ get = (f"https://www.udemy.com/api-2.0/users/me/subscribed-courses/{course_id}/lectures/{id_lecture}/?"
90
+ f"fields[lecture]"
91
+ f"=asset,description,download_url,is_free,last_watched_second&fields[asset]=asset_type,length,"
92
+ f"media_license_token,course_is_drmed,media_sources,captions,thumbnail_sprite,slides,slide_urls,"
93
+ f"download_urls,"
94
+ f"external_url&q=0.3108014137011559/?fields[asset]=download_urls")
95
+ try:
96
+ # Faz a solicitação GET com os cabeçalhos
97
+ response = requests.get(get, headers=HEADERS_USER)
98
+ data = []
99
+ # Exibe o código de status
100
+ if response.status_code == 200:
101
+ a = json.loads(response.text)
102
+ return a
103
+ else:
104
+ UnhandledExceptions(f"erro ao obter dados de aulas!! {response.status_code}")
105
+
106
+ except requests.ConnectionError as e:
107
+ UdemyUserApiExceptions(f"Erro de conexão: {e}")
108
+ except requests.Timeout as e:
109
+ UdemyUserApiExceptions(f"Tempo de requisição excedido: {e}")
110
+ except requests.TooManyRedirects as e:
111
+ UdemyUserApiExceptions(f"Limite de redirecionamentos excedido: {e}")
112
+ except requests.HTTPError as e:
113
+ UdemyUserApiExceptions(f"Erro HTTP: {e}")
114
+ except Exception as e:
115
+ UnhandledExceptions(f"Errro Ao Obter Mídias:{e}")
116
+
117
+
118
+ def remove_tag(d: str):
119
+ new = d.replace("<p>", '').replace("</p>", '').replace('&nbsp;', ' ')
120
+ return new
121
+
122
+
123
+ def extract_files(supplementary_assets: list) -> list:
124
+ """Obtém o ID da lecture, o ID do asset, o asset_type e o filename."""
125
+ files = []
126
+ for item in supplementary_assets:
127
+ lecture_title = item.get('lecture_title')
128
+ lecture_id = item.get('lecture_id')
129
+ asset = item.get('asset', {})
130
+ asset_id = asset.get('id')
131
+ asset_type = asset.get('asset_type')
132
+ filename = asset.get('filename')
133
+ title = asset.get('title')
134
+
135
+ files.append({
136
+ 'lecture_id': lecture_id,
137
+ 'asset_id': asset_id,
138
+ 'asset_type': asset_type,
139
+ 'filename': filename,
140
+ 'title': title,
141
+ 'lecture_title': lecture_title
142
+ })
143
+ return files
144
+
145
+
146
+ def extract_course_data(course_dict) -> dict:
147
+ # Extrair informações principais
148
+ course_id = course_dict.get('id')
149
+ title = course_dict.get('title')
150
+ num_subscribers = course_dict.get('num_subscribers')
151
+ avg_rating_recent = course_dict.get('avg_rating_recent')
152
+ estimated_content_length = course_dict.get('estimated_content_length')
153
+
154
+ # Extrair informações dos instrutores
155
+ instructors = course_dict.get('visible_instructors', [])
156
+ instructor_data = []
157
+ for instructor in instructors:
158
+ instructor_data.append({
159
+ 'id': instructor.get('id'),
160
+ 'title': instructor.get('title'),
161
+ 'name': instructor.get('name'),
162
+ 'display_name': instructor.get('display_name'),
163
+ 'job_title': instructor.get('job_title'),
164
+ 'image_50x50': instructor.get('image_50x50'),
165
+ 'image_100x100': instructor.get('image_100x100'),
166
+ 'initials': instructor.get('initials'),
167
+ 'url': instructor.get('url'),
168
+ })
169
+
170
+ # Extrair informações de localização
171
+ locale = course_dict.get('locale', {})
172
+ locale_data = {
173
+ 'locale': locale.get('locale'),
174
+ 'title': locale.get('title'),
175
+ 'english_title': locale.get('english_title'),
176
+ 'simple_english_title': locale.get('simple_english_title'),
177
+ }
178
+
179
+ # Extrair informações de categorias e subcategorias
180
+ primary_category = course_dict.get('primary_category', {})
181
+ primary_category_data = {
182
+ 'id': primary_category.get('id'),
183
+ 'title': primary_category.get('title'),
184
+ 'title_cleaned': primary_category.get('title_cleaned'),
185
+ 'url': primary_category.get('url'),
186
+ 'icon_class': primary_category.get('icon_class'),
187
+ 'type': primary_category.get('type'),
188
+ }
189
+
190
+ primary_subcategory = course_dict.get('primary_subcategory', {})
191
+ primary_subcategory_data = {
192
+ 'id': primary_subcategory.get('id'),
193
+ 'title': primary_subcategory.get('title'),
194
+ 'title_cleaned': primary_subcategory.get('title_cleaned'),
195
+ 'url': primary_subcategory.get('url'),
196
+ 'icon_class': primary_subcategory.get('icon_class'),
197
+ 'type': primary_subcategory.get('type'),
198
+ }
199
+
200
+ # Extrair informações contextuais
201
+ context_info = course_dict.get('context_info', {})
202
+ category_info = context_info.get('category', {})
203
+ label_info = context_info.get('label', {})
204
+
205
+ category_data = {
206
+ 'id': category_info.get('id'),
207
+ 'title': category_info.get('title'),
208
+ 'url': category_info.get('url'),
209
+ 'tracking_object_type': category_info.get('tracking_object_type'),
210
+ }
211
+
212
+ label_data = {
213
+ 'id': label_info.get('id'),
214
+ 'display_name': label_info.get('display_name'),
215
+ 'title': label_info.get('title'),
216
+ 'topic_channel_url': label_info.get('topic_channel_url'),
217
+ 'url': label_info.get('url'),
218
+ 'tracking_object_type': label_info.get('tracking_object_type'),
219
+ }
220
+
221
+ # Compilar todos os dados em um dicionário
222
+ result = {
223
+ 'course_id': course_id,
224
+ 'title': title,
225
+ 'num_subscribers': num_subscribers,
226
+ 'avg_rating_recent': avg_rating_recent,
227
+ 'estimated_content_length': estimated_content_length,
228
+ 'instructors': instructor_data,
229
+ 'locale': locale_data,
230
+ 'primary_category': primary_category_data,
231
+ 'primary_subcategory': primary_subcategory_data,
232
+ 'category_info': category_data,
233
+ 'label_info': label_data,
234
+ }
235
+
236
+ return result
237
+
238
+
239
+ def format_size(byte_size):
240
+ # Constantes para conversão
241
+ KB = 1024
242
+ MB = KB ** 2
243
+ GB = KB ** 3
244
+ TB = KB ** 4
245
+ try:
246
+ byte_size = int(byte_size)
247
+
248
+ if byte_size < KB:
249
+ return f"{byte_size} bytes"
250
+ elif byte_size < MB:
251
+ return f"{byte_size / KB:.2f} KB"
252
+ elif byte_size < GB:
253
+ return f"{byte_size / MB:.2f} MB"
254
+ elif byte_size < TB:
255
+ return f"{byte_size / GB:.2f} GB"
256
+ else:
257
+ return f"{byte_size / TB:.2f} TB"
258
+ except Exception as e:
259
+ return byte_size
@@ -0,0 +1,162 @@
1
+ import json
2
+ import os
3
+ import pickle
4
+ import requests
5
+ from .exeptions import UnhandledExceptions, UdemyUserApiExceptions, LoginException
6
+ import cloudscraper
7
+
8
+
9
+ class UdemyAuth:
10
+ def __init__(self):
11
+ """Autenticação na plataforma udemy de maneira segura, atencao ao limite de logins,recomendo que apos logar
12
+ nao use novamnete o metodo login use apenas o verifcador de login para evitar bloqueios temporários..."""
13
+ self.__cookie_dict = {}
14
+ dir_out = '.udemy_userAPI'
15
+ os.makedirs(os.path.join(os.path.expanduser('~'), dir_out), exist_ok=True)
16
+ self.__user_dir = os.path.join(os.path.expanduser('~'), dir_out)
17
+
18
+ def __make_cookies(self, client_id: str, access_token: str, csrf_token: str):
19
+ self.__cookie_dict = {
20
+ 'client_id': client_id,
21
+ 'access_token': access_token,
22
+ 'csrf_token': csrf_token
23
+ }
24
+
25
+ @property
26
+ def verif_login(self):
27
+ """verificar se o usuario estar logado."""
28
+
29
+ def verif_config():
30
+ # Verificar se o arquivo .userLogin existe e carregar cookies se existir
31
+ try:
32
+ with open(fr'{self.__user_dir}\.Udemy_userLogin', 'rb') as f:
33
+ cookies = pickle.load(f)
34
+ cookies_dict = {cookie.name: cookie.value for cookie in cookies}
35
+ cookies_str = "; ".join([f"{key}={value}" for key, value in cookies_dict.items()])
36
+ return cookies_str
37
+ except FileNotFoundError:
38
+ return False
39
+
40
+ log = verif_config()
41
+
42
+ if log:
43
+ cookies_de_secao = log
44
+ headers = {
45
+ "accept": "*/*",
46
+ "accept-language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
47
+ "cache-control": "no-cache",
48
+ "Content-Type": "text/plain",
49
+ "pragma": "no-cache",
50
+ "sec-ch-ua": "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"",
51
+ "sec-ch-ua-mobile": "?0",
52
+ "sec-ch-ua-platform": "\"Windows\"",
53
+ "sec-fetch-dest": "empty",
54
+ "sec-fetch-mode": "cors",
55
+ "sec-fetch-site": "cross-site",
56
+ "Cookie": cookies_de_secao,
57
+ "Referer": "https://www.udemy.com/"
58
+ }
59
+
60
+ try:
61
+ url = 'https://www.udemy.com/api-2.0/contexts/me/?header=true'
62
+ resp = requests.get(url=url, headers=headers)
63
+ if resp.status_code == 200:
64
+ convert = json.loads(resp.text)
65
+ if convert.get('header', {}).get('isLoggedIn', False):
66
+ return True
67
+ else:
68
+ return False
69
+ except requests.ConnectionError as e:
70
+ raise UdemyUserApiExceptions(f"Erro de conexão: {e}")
71
+ except requests.Timeout as e:
72
+ raise UdemyUserApiExceptions(f"Tempo de requisição excedido: {e}")
73
+ except requests.TooManyRedirects as e:
74
+ raise UdemyUserApiExceptions(f"Limite de redirecionamentos excedido: {e}")
75
+ except requests.HTTPError as e:
76
+ raise UdemyUserApiExceptions(f"Erro HTTP: {e}")
77
+ except Exception as e:
78
+ raise UnhandledExceptions(f"Unhandled-ERROR: {e}")
79
+ else:
80
+ return False
81
+
82
+ def login(self, email: str, password: str):
83
+ """efetuar login na udemy"""
84
+ # Inicializa uma sessão usando cloudscraper para contornar a proteção Cloudflare
85
+ s = cloudscraper.create_scraper()
86
+
87
+ # Faz uma requisição GET à página de inscrição para obter o token CSRF
88
+ r = s.get(
89
+ "https://www.udemy.com/join/signup-popup/",
90
+ headers={"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"},
91
+ )
92
+
93
+ # Extrai o token CSRF dos cookies
94
+ csrf_token = r.cookies["csrftoken"]
95
+
96
+ # Prepara os dados para o login
97
+ data = {
98
+ "csrfmiddlewaretoken": csrf_token,
99
+ "locale": "pt_BR",
100
+ "email": email,
101
+ "password": password,
102
+ }
103
+
104
+ # Atualiza os cookies e cabeçalhos da sessão
105
+ s.cookies.update(r.cookies)
106
+ s.headers.update(
107
+ {
108
+ "User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
109
+ "Accept": "application/json, text/plain, */*",
110
+ "Accept-Language": "en-GB,en;q=0.5",
111
+ "Referer": "https://www.udemy.com/join/login-popup/?locale=en_US&response_type=html&next=https%3A%2F"
112
+ "%2Fwww.udemy.com%2F",
113
+ "Origin": "https://www.udemy.com",
114
+ "DNT": "1",
115
+ "Connection": "keep-alive",
116
+ "Sec-Fetch-Dest": "empty",
117
+ "Sec-Fetch-Mode": "cors",
118
+ "Sec-Fetch-Site": "same-origin",
119
+ "Pragma": "no-cache",
120
+ "Cache-Control": "no-cache",
121
+ }
122
+ )
123
+
124
+ # Tenta fazer login com as credenciais fornecidas
125
+ r = s.post(
126
+ "https://www.udemy.com/join/login-popup/?response_type=json",
127
+ data=data,
128
+ allow_redirects=False,
129
+ )
130
+
131
+ # Verifica a resposta para determinar se o login foi bem-sucedido
132
+ if "returnUrl" in r.text:
133
+ self.__make_cookies(r.cookies.get("client_id"), r.cookies.get("access_token"), csrf_token)
134
+ self.__save_cookies(s.cookies)
135
+ else:
136
+ login_error = r.json().get("error", {}).get("data", {}).get("formErrors", [])[0]
137
+ if login_error[0] == "Y":
138
+ raise LoginException("Você excedeu o número máximo de solicitações por hora.")
139
+ elif login_error[0] == "T":
140
+ raise LoginException("Email ou senha incorretos")
141
+ else:
142
+ raise UnhandledExceptions(login_error)
143
+
144
+ return s
145
+
146
+ def __save_cookies(self, cookies):
147
+ with open(fr'{self.__user_dir}\.Udemy_userLogin', 'wb') as f:
148
+ pickle.dump(cookies, f)
149
+
150
+ @property
151
+ def load_cookies(self) -> str:
152
+ """carrega cookies e retorna-os em uma string formatada"""
153
+ file = os.path.join(fr'{self.__user_dir}\.Udemy_userLogin')
154
+ if os.path.exists(file):
155
+ with open(fr'{self.__user_dir}\.Udemy_userLogin', 'rb') as f:
156
+ cookies = pickle.load(f)
157
+ cookies_dict = {cookie.name: cookie.value for cookie in cookies}
158
+ cookies_str = "; ".join([f"{key}={value}" for key, value in cookies_dict.items()])
159
+ return cookies_str
160
+ else:
161
+ raise LoginException()
162
+
@@ -0,0 +1,210 @@
1
+ import requests
2
+ import json
3
+ from .sections import get_course_infor
4
+ from .api import get_links, remove_tag, parser_chapers, extract_files, HEADERS_USER
5
+
6
+
7
+
8
+ class Files:
9
+ def __init__(self, files: list[dict], id_course):
10
+ self.__data = files
11
+ self.__id_course = id_course
12
+
13
+ @property
14
+ def get_download_url(self) -> dict[list]:
15
+ """obter url de download de um arquivo quando disponivel(geralemnete para arquivos esta opção é valida"""
16
+ da = {}
17
+ download_urls = ''
18
+ for files in self.__data:
19
+ lecture_id = files.get('lecture_id', None)
20
+ asset_id = files.get('asset_id', None)
21
+ title = files.get("title", None)
22
+ if asset_id and title and lecture_id:
23
+ resp = requests.get(
24
+ f"https://www.udemy.com/api-2.0/users/me/subscribed-courses/{self.__id_course}/lectures/{lecture_id}/supplementary-assets/{asset_id}/?fields[asset]=download_urls",
25
+ headers=HEADERS_USER)
26
+ if resp.status_code == 200:
27
+ da = json.loads(resp.text)
28
+ download_urls = da['download_urls']
29
+ return download_urls
30
+
31
+
32
+ class Lecture:
33
+ """CRIAR objetos aula(lecture) do curso e extrair os dados.."""
34
+
35
+ def __init__(self, data: dict):
36
+ self.__data = data
37
+ self.__asset = self.__data.get("asset")
38
+
39
+ @property
40
+ def get_lecture_id(self) -> int:
41
+ """Obtém o ID da lecture"""
42
+ return self.__data.get('id')
43
+
44
+ @property
45
+ def get_description(self) -> str:
46
+ """Obtém a descrição da aula"""
47
+ return remove_tag(str(self.__data.get('description')))
48
+
49
+ @property
50
+ def is_free(self) -> bool:
51
+ """Verifica se a lecture é gratuita (aulas gratís estão disponíveis na apresentação do curso)"""
52
+ return self.__data.get('is_free', False)
53
+
54
+ @property
55
+ def get_thumbnail(self) -> dict:
56
+ """Obtém informações da miniatura (thumbnail) do vídeo"""
57
+ thumbnail_sprite = self.__asset.get('thumbnail_sprite', {})
58
+ return {
59
+ 'thumbnail_vtt_url': thumbnail_sprite.get('vtt_url'),
60
+ 'thumbnail_img_url': thumbnail_sprite.get('img_url')
61
+ }
62
+
63
+ @property
64
+ def get_asset_type(self) -> str:
65
+ """Obtém o tipo de asset (Video, Article, etc.)"""
66
+ return self.__asset.get('asset_type', 'Undefined')
67
+
68
+ @property
69
+ def get_media_sources(self) -> list:
70
+ """obtém dados de streaming"""
71
+ return self.__asset.get('media_sources')
72
+
73
+ @property
74
+ def get_captions(self) -> list:
75
+ """obtem as legendas"""
76
+ return self.__asset.get('captions')
77
+
78
+ @property
79
+ def get_external_url(self) -> list:
80
+ """obtem links externos se tiver..."""
81
+ return self.__asset.get('external_url')
82
+
83
+ @property
84
+ def get_media_license_token(self) -> str:
85
+ """obtem token de acesso a aula se tiver.."""
86
+ return self.__asset.get('media_license_token')
87
+
88
+ @property
89
+ def course_is_drmed(self) -> bool:
90
+ """verifica se possui DRM.."""
91
+ return self.__asset.get('course_is_drmed')
92
+
93
+ @property
94
+ def get_download_urls(self) -> list:
95
+ """obtém urls de downloads se tiver.."""
96
+ return self.__asset.get('download_urls')
97
+
98
+ @property
99
+ def get_slide_urls(self) -> list:
100
+ """obtém url de slides se tiver..."""
101
+ return self.__asset.get('slide_urls')
102
+
103
+ @property
104
+ def get_slides(self) -> list:
105
+ """obtem slides se tiver.."""
106
+ return self.__asset.get('slides')
107
+
108
+
109
+ class Course:
110
+ """receb um dict com os dados do curso"""
111
+
112
+ def __init__(self, results: dict, course_id: int):
113
+ self.__parser_chapers = parser_chapers(results=results)
114
+ self.__data = self.__parser_chapers
115
+ self.__course_id = course_id
116
+ self.__results = results
117
+ self.__information = self.__load_infor_course()
118
+
119
+ def __load_infor_course(self) -> dict:
120
+ """obtem as informações do curso"""
121
+ data = get_course_infor(self.__course_id)
122
+ return data
123
+
124
+ @property
125
+ def title_course(self):
126
+ """obter titulo do curso"""
127
+ return self.__information.get('title')
128
+
129
+ @property
130
+ def instructors(self):
131
+ """obter informações de instrutores"""
132
+ return self.__information.get("visible_instructors")
133
+
134
+ @property
135
+ def locale(self):
136
+ """obter informações de localidade do curso"""
137
+ return self.__information.get('locale')
138
+
139
+ @property
140
+ def primary_category(self):
141
+ """obter categoria primaria"""
142
+ return self.__information.get('primary_category')
143
+
144
+ @property
145
+ def primary_subcategory(self):
146
+ """obter subcategoria primaria"""
147
+ return self.__information.get('primary_subcategory')
148
+
149
+ @property
150
+ def count_lectures(self) -> int:
151
+ """Obtém o número total de lectures no curso"""
152
+ total_lectures = 0
153
+ for chapter in self.__data.values():
154
+ total_lectures += len(chapter.get('videos_in_chapter', []))
155
+ return total_lectures
156
+
157
+ @property
158
+ def count_chapters(self) -> int:
159
+ """Obtém o número total de chapters(sections) no curso"""
160
+ return len(self.__data)
161
+
162
+ @property
163
+ def title_videos(self) -> list:
164
+ """Obtém uma lista com todos os títulos de vídeos no curso"""
165
+ videos = []
166
+ for chapter in self.__data.values():
167
+ for video in chapter.get('videos_in_chapter', []):
168
+ title = video['video_title']
169
+ if title != "Files":
170
+ videos.append(title)
171
+ return videos
172
+
173
+ @property
174
+ def get_lectures(self) -> list:
175
+ """Obtém uma lista com todos as aulas"""
176
+ videos = []
177
+ for chapter in self.__data.values():
178
+ for video in chapter.get('videos_in_chapter', []):
179
+ title = video['video_title']
180
+ id_lecture = video['id_lecture']
181
+ id_asset = video['id_asset']
182
+ dt = {"title": title, 'id_lecture': id_lecture, 'id_asset': id_asset}
183
+ videos.append(dt)
184
+ return videos
185
+
186
+ def get_details_lecture(self, lecture_id: int) -> Lecture:
187
+ """obter detalhes de uma aula específica, irá retornar o objeto Lecture"""
188
+ links = get_links(course_id=self.__course_id, id_lecture=lecture_id)
189
+ lecture = Lecture(data=links)
190
+ return lecture
191
+
192
+ @property
193
+ def get_additional_files(self) -> dict[list]:
194
+ """Retorna a lista de arquivos adcionais de um curso."""
195
+ supplementary_assets = []
196
+ for item in self.__results.get('results', []):
197
+ # Check if the item is a lecture with supplementary assets
198
+ if item.get('_class') == 'lecture':
199
+ id = item.get('id')
200
+ title = item.get('title')
201
+ assets = item.get('supplementary_assets', [])
202
+ for asset in assets:
203
+ supplementary_assets.append({
204
+ 'lecture_id': id,
205
+ 'lecture_title': title,
206
+ 'asset': asset
207
+ })
208
+ files = extract_files(supplementary_assets)
209
+ files_objt = Files(files=files, id_course=self.__course_id).get_download_url
210
+ return files_objt
@@ -0,0 +1,17 @@
1
+
2
+ class UdemyUserApiExceptions(Exception):
3
+ def __init__(self, message="Udemy_UserApi Generic Error!"):
4
+ self.message = message
5
+ super().__init__(self.message)
6
+
7
+
8
+ class UnhandledExceptions(Exception):
9
+ def __init__(self, message="Error Unhandled!"):
10
+ self.message = message
11
+ super().__init__(self.message)
12
+
13
+
14
+ class LoginException(Exception):
15
+ def __init__(self, message="Error Login!"):
16
+ self.message = message
17
+ super().__init__(self.message)
@@ -0,0 +1,75 @@
1
+ import json
2
+ import requests
3
+ from .exeptions import UdemyUserApiExceptions, LoginException
4
+ from .api import HEADERS_USER
5
+
6
+
7
+ def get_courses_plan(tipe: str) -> list:
8
+ courses_data = []
9
+ if tipe == 'default':
10
+ response = requests.get(f"https://www.udemy.com/api-2.0/users/me/subscribed-courses/?page_size=1000"
11
+ f"&ordering=-last_accessed&fields[course]=image_240x135,title,completion_ratio&"
12
+ f"is_archived=false",
13
+ headers=HEADERS_USER)
14
+ if response.status_code == 200:
15
+ r = json.loads(response.text)
16
+ results = r.get("results", None)
17
+ if results:
18
+ courses_data.append(results)
19
+ else:
20
+ r = json.loads(response.text)
21
+ results = r.get("detail")
22
+ raise UdemyUserApiExceptions(f"Error obtain courses plans -> {results}")
23
+ elif tipe == 'plan':
24
+ response2 = requests.get(
25
+ url="https://www.udemy.com/api-2.0/users/me/subscription-course-enrollments/?"
26
+ "fields[course]=@min,visible_instructors,image_240x135,image_480x270,completion_ratio,"
27
+ "last_accessed_time,enrollment_time,is_practice_test_course,features,num_collections,"
28
+ "published_title,buyable_object_type,remaining_time,is_assigned,next_to_watch_item,"
29
+ "is_in_user_subscription&fields[user]=@min&ordering=-last_accessed&page_size=1000&"
30
+ "max_progress=99.9&fields[lecture]=@min,content_details,asset,url,thumbnail_url,"
31
+ "last_watched_second,object_index&fields[quiz]=@min,content_details,asset,url,object_index&"
32
+ "fields[practice]=@min,content_details,asset,estimated_duration,learn_url,object_index",
33
+ headers=HEADERS_USER)
34
+ if response2.status_code == 200:
35
+ r = json.loads(response2.text)
36
+ results2 = r.get("results", None)
37
+ if results2:
38
+ courses_data.append(results2)
39
+ else:
40
+ r = json.loads(response2.text)
41
+ results = r.get("detail")
42
+ raise UdemyUserApiExceptions(f"Error obtain courses plans2 -> {results}")
43
+ else:
44
+ raise UdemyUserApiExceptions("Atenção dev! os parametros são : 'plan' e 'default'")
45
+ return courses_data
46
+
47
+
48
+ def get_details_courses(course_id):
49
+ response = requests.get(
50
+ f"https://www.udemy.com/api-2.0/courses/{course_id}/subscriber-curriculum-items/?"
51
+ f"caching_intent=True&fields%5Basset%5D=title%2Cfilename%2Casset_type%2Cstatus%2Ctime_estimation%2"
52
+ f"Cis_external&fields%5Bchapter%5D=title%2Cobject_index%2Cis_published%2Csort_order&fields%5Blecture"
53
+ f"%5D=title%2Cobject_index%2Cis_published%2Csort_order%2Ccreated%2Casset%2Csupplementary_assets%2"
54
+ f"Cis_free&fields%5Bpractice%5D=title%2Cobject_index%2Cis_published%2Csort_order&fields%5Bquiz%5D="
55
+ f"title%2Cobject_index%2Cis_published%2Csort_order%2Ctype&pages&page_size=400&fields[lecture]=asset,"
56
+ f"description,download_url,is_free,last_watched_second&fields[asset]=asset_type,length,"
57
+ f"media_license_token,course_is_drmed,external_url&q=0.3108014137011559",
58
+ headers=HEADERS_USER)
59
+ if response.status_code == 200:
60
+ resposta = json.loads(response.text)
61
+ return resposta
62
+ else:
63
+ UdemyUserApiExceptions(f"erro ao obter detalhes do curso! {response.status_code}")
64
+
65
+
66
+ def get_course_infor(course_id):
67
+ end_point = (
68
+ f'https://www.udemy.com/api-2.0/courses/{course_id}/?fields[course]=title,context_info,primary_category,'
69
+ 'primary_subcategory,avg_rating_recent,visible_instructors,locale,estimated_content_length,'
70
+ 'num_subscribers')
71
+ response = requests.get(end_point, headers=HEADERS_USER)
72
+ if response.status_code == 200:
73
+ return json.loads(response.text)
74
+ else:
75
+ raise UdemyUserApiExceptions("erro ao obter informações do curso!")
@@ -0,0 +1,48 @@
1
+ from .exeptions import UdemyUserApiExceptions, UnhandledExceptions, LoginException
2
+ from .sections import get_courses_plan, get_details_courses
3
+ from .api import HEADERS_USER
4
+ from .bultins import Course
5
+ from .authenticate import UdemyAuth
6
+
7
+ auth = UdemyAuth()
8
+ verif_login = auth.verif_login
9
+
10
+
11
+ class Udemy:
12
+ """wrapper para api de usuario da plataforma udemy"""
13
+
14
+ def __init__(self):
15
+ """
16
+ cookies de seção.
17
+ """
18
+ self.__headers = HEADERS_USER
19
+ if verif_login is None:
20
+ raise LoginException("User Not Logged!")
21
+
22
+ @property
23
+ def my_subscribed_courses_by_plan(self) -> list[dict]:
24
+ """obtém os cursos que o usuário esatá inscrito, obtidos atraves de planos(assinatura)"""
25
+ try:
26
+ courses = get_courses_plan(tipe='plan')
27
+ return courses
28
+ except UdemyUserApiExceptions as e:
29
+ UnhandledExceptions(e)
30
+
31
+ @property
32
+ def my_subscribed_courses(self) -> list[dict]:
33
+ """obtém os cursos que o usuário esatá inscrito"""
34
+ try:
35
+ courses = get_courses_plan(tipe='default')
36
+ return courses
37
+ except UdemyUserApiExceptions as e:
38
+ UnhandledExceptions(e)
39
+
40
+ @staticmethod
41
+ def get_details_course(course_id):
42
+ """obtenha detalhes de um curso atarves do id"""
43
+ try:
44
+ d = get_details_courses(course_id)
45
+ b = Course(course_id=course_id, results=d)
46
+ return b
47
+ except UnhandledExceptions as e:
48
+ UnhandledExceptions(e)
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.1
2
+ Name: udemy_userAPI
3
+ Version: 0.1
4
+ Summary: Obtenha detalhes de cursos que o usuário esteja inscrito da plataforma Udemy,usando o EndPoint de
5
+ Author: PauloCesar-dev404
6
+ Author-email: paulocesar0073dev404@gmail.com
7
+ License: MIT
8
+ Project-URL: Código Fonte, https://raw.githubusercontent.com/PauloCesar-dev404/udemy_userAPI/main/udemy_userAPI-0.1.tar.gz
9
+ Project-URL: lib, https://raw.githubusercontent.com/PauloCesar-dev404/udemy_userAPI/main/udemy_userAPI-0.1-py3-none-any.whl
10
+ Project-URL: GitHub, https://github.com/PauloCesar-dev404/udemy_userAPI
11
+ Project-URL: Bugs/Melhorias, https://github.com/PauloCesar-dev404/udemy_userAPI/issues
12
+ Project-URL: Documentação, https://github.com/PauloCesar-dev404/udemy_userAPI/wiki
13
+ Keywords: udemy,udemy python,pyudemy,udemy_userAPI,udemy api
14
+ Platform: any
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests
18
+ Requires-Dist: cloudscraper
19
+
20
+ # udemy-userAPI
21
+
22
+
23
+ ![Versão](https://img.shields.io/badge/version-0.1-orange)
24
+ ![Licença](https://img.shields.io/badge/license-MIT-orange)
25
+ [![Sponsor](https://img.shields.io/badge/💲Donate-yellow)](https://apoia.se/paulocesar-dev404)
26
+ [![Sponsor](https://img.shields.io/badge/Documentation-green)](https://github.com/PauloCesar-dev404/udemy-userAPI/wiki)
27
+
28
+
29
+ Obtenha detalhes de cursos da plataforma udemy com a api de usuário,usando esta lib
30
+ ---
31
+ - [x] Obter cursos inscritos(acesso por plano ou cursos free)
32
+ - [x] Obter detalhes de Aulas
33
+ - [x] Obter detalhes de um Curso
34
+
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ README_PYPI.md
5
+ setup.py
6
+ udemy_userAPI/__init__.py
7
+ udemy_userAPI/__version__.py
8
+ udemy_userAPI/api.py
9
+ udemy_userAPI/authenticate.py
10
+ udemy_userAPI/bultins.py
11
+ udemy_userAPI/exeptions.py
12
+ udemy_userAPI/sections.py
13
+ udemy_userAPI/udemy.py
14
+ udemy_userAPI.egg-info/PKG-INFO
15
+ udemy_userAPI.egg-info/SOURCES.txt
16
+ udemy_userAPI.egg-info/dependency_links.txt
17
+ udemy_userAPI.egg-info/not-zip-safe
18
+ udemy_userAPI.egg-info/requires.txt
19
+ udemy_userAPI.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ requests
2
+ cloudscraper
@@ -0,0 +1 @@
1
+ udemy_userAPI