udemy-userAPI 0.1__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- udemy_userapi-0.1/LICENSE +21 -0
- udemy_userapi-0.1/MANIFEST.in +3 -0
- udemy_userapi-0.1/PKG-INFO +34 -0
- udemy_userapi-0.1/README.md +25 -0
- udemy_userapi-0.1/README_PYPI.md +15 -0
- udemy_userapi-0.1/setup.cfg +4 -0
- udemy_userapi-0.1/setup.py +34 -0
- udemy_userapi-0.1/udemy_userAPI/__init__.py +7 -0
- udemy_userapi-0.1/udemy_userAPI/__version__.py +11 -0
- udemy_userapi-0.1/udemy_userAPI/api.py +259 -0
- udemy_userapi-0.1/udemy_userAPI/authenticate.py +162 -0
- udemy_userapi-0.1/udemy_userAPI/bultins.py +210 -0
- udemy_userapi-0.1/udemy_userAPI/exeptions.py +17 -0
- udemy_userapi-0.1/udemy_userAPI/sections.py +75 -0
- udemy_userapi-0.1/udemy_userAPI/udemy.py +48 -0
- udemy_userapi-0.1/udemy_userAPI.egg-info/PKG-INFO +34 -0
- udemy_userapi-0.1/udemy_userAPI.egg-info/SOURCES.txt +19 -0
- udemy_userapi-0.1/udemy_userAPI.egg-info/dependency_links.txt +1 -0
- udemy_userapi-0.1/udemy_userAPI.egg-info/not-zip-safe +1 -0
- udemy_userapi-0.1/udemy_userAPI.egg-info/requires.txt +2 -0
- udemy_userapi-0.1/udemy_userAPI.egg-info/top_level.txt +1 -0
@@ -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,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
|
+

|
24
|
+

|
25
|
+
[](https://apoia.se/paulocesar-dev404)
|
26
|
+
[](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
|
+

|
6
|
+

|
7
|
+
[](https://apoia.se/paulocesar-dev404)
|
8
|
+
[](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
|
+

|
5
|
+

|
6
|
+
[](https://apoia.se/paulocesar-dev404)
|
7
|
+
[](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,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,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(' ', ' ')
|
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
|
+

|
24
|
+

|
25
|
+
[](https://apoia.se/paulocesar-dev404)
|
26
|
+
[](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 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
udemy_userAPI
|