udemy-userAPI 0.1__tar.gz

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,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