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