udemy-userAPI 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- animation_consoles/__init__.py +1 -0
- animation_consoles/animation.py +64 -0
- ffmpeg_for_python/__config__.py +118 -0
- ffmpeg_for_python/__init__.py +8 -0
- ffmpeg_for_python/__utils.py +78 -0
- ffmpeg_for_python/__version__.py +6 -0
- ffmpeg_for_python/exeptions.py +91 -0
- ffmpeg_for_python/ffmpeg.py +203 -0
- m3u8_analyzer/M3u8Analyzer.py +807 -0
- m3u8_analyzer/__init__.py +7 -0
- m3u8_analyzer/__version__.py +1 -0
- m3u8_analyzer/exeptions.py +82 -0
- udemy_userAPI/.cache/.udemy_userAPI +0 -0
- udemy_userAPI/__init__.py +7 -0
- udemy_userAPI/__version__.py +6 -0
- udemy_userAPI/api.py +691 -0
- udemy_userAPI/authenticate.py +311 -0
- udemy_userAPI/bultins.py +495 -0
- udemy_userAPI/exeptions.py +22 -0
- udemy_userAPI/mpd_analyzer/__init__.py +3 -0
- udemy_userAPI/mpd_analyzer/bin.wvd +0 -0
- udemy_userAPI/mpd_analyzer/mpd_parser.py +224 -0
- udemy_userAPI/sections.py +117 -0
- udemy_userAPI/udemy.py +93 -0
- udemy_userAPI-0.3.2.dist-info/LICENSE +21 -0
- udemy_userAPI-0.3.2.dist-info/METADATA +34 -0
- udemy_userAPI-0.3.2.dist-info/RECORD +29 -0
- udemy_userAPI-0.3.2.dist-info/WHEEL +5 -0
- udemy_userAPI-0.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,224 @@
|
|
1
|
+
import re
|
2
|
+
import xml.etree.ElementTree as Et
|
3
|
+
|
4
|
+
|
5
|
+
def calculate_segment_url2(media_template, segment_number, segment_time, rep_id):
|
6
|
+
"""
|
7
|
+
Calcula a URL de um segmento específico, substituindo variáveis no template.
|
8
|
+
"""
|
9
|
+
url = media_template.replace('$Number$', str(segment_number))
|
10
|
+
url = url.replace('$RepresentationID$', rep_id)
|
11
|
+
if '$Time$' in url:
|
12
|
+
url = url.replace('$Time$', str(segment_time))
|
13
|
+
return url
|
14
|
+
|
15
|
+
|
16
|
+
def build_url2(template, rep_id):
|
17
|
+
"""
|
18
|
+
Constrói a URL substituindo variáveis no template com base nos atributos.
|
19
|
+
"""
|
20
|
+
if '$RepresentationID$' in template:
|
21
|
+
template = template.replace('$RepresentationID$', rep_id)
|
22
|
+
return template
|
23
|
+
|
24
|
+
|
25
|
+
def parse_duration(duration_str):
|
26
|
+
"""
|
27
|
+
Converte uma duração em formato ISO 8601 (ex: "PT163.633S") para segundos (float).
|
28
|
+
"""
|
29
|
+
match = re.match(r'PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?', duration_str)
|
30
|
+
if match:
|
31
|
+
hours = int(match.group(1)) if match.group(1) else 0
|
32
|
+
minutes = int(match.group(2)) if match.group(2) else 0
|
33
|
+
seconds = float(match.group(3)) if match.group(3) else 0.0
|
34
|
+
return hours * 3600 + minutes * 60 + seconds
|
35
|
+
return 0.0
|
36
|
+
|
37
|
+
|
38
|
+
class MPDParser:
|
39
|
+
"""
|
40
|
+
Classe para analisar e extrair informações de manifestos MPD (Media Presentation Description),
|
41
|
+
com foco em arquivos VOD (Video on Demand). Atualmente, não oferece suporte para transmissões ao vivo.
|
42
|
+
"""
|
43
|
+
|
44
|
+
def __init__(self, mpd_content: str):
|
45
|
+
"""
|
46
|
+
Inicializa o parser para um arquivo MPD.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
mpd_content (str): Caminho do arquivo MPD ou conteúdo bruto.
|
50
|
+
"""
|
51
|
+
self._mpd_content = mpd_content
|
52
|
+
self._video_representations = {}
|
53
|
+
self._audio_representations = {}
|
54
|
+
self._content_protection = {}
|
55
|
+
self._selected_resolution = None
|
56
|
+
|
57
|
+
# Tenta fazer o parsing com diferentes métodos
|
58
|
+
if not self.__parse_mpd_v2():
|
59
|
+
self.__parse_mpd_v1()
|
60
|
+
|
61
|
+
def __parse_mpd_v1(self):
|
62
|
+
"""
|
63
|
+
Parsing básico do MPD (versão 1).
|
64
|
+
"""
|
65
|
+
content = self._mpd_content
|
66
|
+
root = Et.fromstring(content)
|
67
|
+
ns = {'dash': 'urn:mpeg:dash:schema:mpd:2011'}
|
68
|
+
|
69
|
+
for adaptation_set in root.findall('.//dash:AdaptationSet', ns):
|
70
|
+
mime_type = adaptation_set.attrib.get('mimeType', '')
|
71
|
+
self.__parse_adaptation_set(adaptation_set, mime_type, ns)
|
72
|
+
|
73
|
+
def __parse_mpd_v2(self):
|
74
|
+
"""
|
75
|
+
Parsing avançado do MPD (versão 2).
|
76
|
+
"""
|
77
|
+
content = self._mpd_content
|
78
|
+
root = Et.fromstring(content)
|
79
|
+
ns = {'dash': 'urn:mpeg:dash:schema:mpd:2011'}
|
80
|
+
|
81
|
+
for adaptation_set in root.findall('.//dash:AdaptationSet', ns):
|
82
|
+
mime_type = adaptation_set.attrib.get('mimeType', '')
|
83
|
+
self.__parse_adaptation_set(adaptation_set, mime_type, ns)
|
84
|
+
return True
|
85
|
+
|
86
|
+
def __parse_adaptation_set(self, adaptation_set, mime_type, ns):
|
87
|
+
"""
|
88
|
+
Analisa um AdaptationSet para representações de vídeo ou áudio.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
adaptation_set (ET.Element): Elemento do AdaptationSet.
|
92
|
+
mime_type (str): Tipo MIME (vídeo ou áudio).
|
93
|
+
ns (dict): Namespace para parsing do XML.
|
94
|
+
"""
|
95
|
+
# Extrai informações de proteção de conteúdo
|
96
|
+
for content_protection in adaptation_set.findall('dash:ContentProtection', ns):
|
97
|
+
scheme_id_uri = content_protection.attrib.get('schemeIdUri', '')
|
98
|
+
value = content_protection.attrib.get('value', '')
|
99
|
+
self._content_protection[scheme_id_uri] = value
|
100
|
+
|
101
|
+
# Processa representações dentro do AdaptationSet
|
102
|
+
for representation in adaptation_set.findall('dash:Representation', ns):
|
103
|
+
self.__process_representation(representation, mime_type, ns)
|
104
|
+
|
105
|
+
def __process_representation(self, representation, mime_type, ns):
|
106
|
+
"""
|
107
|
+
Processa uma representação de mídia (vídeo ou áudio).
|
108
|
+
|
109
|
+
Args:
|
110
|
+
representation (ET.Element): Elemento da Representação.
|
111
|
+
mime_type (str): Tipo MIME da mídia.
|
112
|
+
ns (dict): Namespace para parsing do XML.
|
113
|
+
"""
|
114
|
+
rep_id = representation.attrib.get('id')
|
115
|
+
width = int(representation.attrib.get('width', 0))
|
116
|
+
height = int(representation.attrib.get('height', 0))
|
117
|
+
resolution = (width, height) if width and height else None
|
118
|
+
bandwidth = int(representation.attrib.get('bandwidth', 0))
|
119
|
+
|
120
|
+
# Extrai informações do SegmentTemplate
|
121
|
+
segment_template = representation.find('dash:SegmentTemplate', ns)
|
122
|
+
if segment_template:
|
123
|
+
init_url = self.__build_url(segment_template.get('initialization'), rep_id, bandwidth)
|
124
|
+
segments = self.__generate_segments(segment_template, ns, rep_id, bandwidth)
|
125
|
+
|
126
|
+
representation_info = {
|
127
|
+
'id': rep_id,
|
128
|
+
'resolution': resolution,
|
129
|
+
'bandwidth': bandwidth,
|
130
|
+
'init_url': init_url,
|
131
|
+
'segments': segments,
|
132
|
+
}
|
133
|
+
if 'video' in mime_type:
|
134
|
+
self._video_representations[resolution] = representation_info
|
135
|
+
elif 'audio' in mime_type:
|
136
|
+
self._audio_representations[bandwidth] = representation_info
|
137
|
+
|
138
|
+
def __generate_segments(self, segment_template, ns, rep_id, bandwidth):
|
139
|
+
"""
|
140
|
+
Gera a lista de URLs de segmentos com base no SegmentTemplate.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
segment_template (ET.Element): Elemento do SegmentTemplate.
|
144
|
+
ns (dict): Namespace para parsing do XML.
|
145
|
+
rep_id (str): ID da representação.
|
146
|
+
bandwidth (int): Largura de banda da representação.
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
list: URLs dos segmentos.
|
150
|
+
"""
|
151
|
+
segments = []
|
152
|
+
media_template = segment_template.get('media')
|
153
|
+
segment_timeline = segment_template.find('dash:SegmentTimeline', ns)
|
154
|
+
|
155
|
+
if segment_timeline:
|
156
|
+
segment_number = int(segment_template.get('startNumber', 1))
|
157
|
+
for segment in segment_timeline.findall('dash:S', ns):
|
158
|
+
t = int(segment.get('t', 0))
|
159
|
+
d = int(segment.get('d'))
|
160
|
+
r = int(segment.get('r', 0))
|
161
|
+
for i in range(r + 1):
|
162
|
+
segment_time = t + i * d
|
163
|
+
segments.append(self.__build_url(media_template, rep_id, bandwidth, segment_time, segment_number))
|
164
|
+
segment_number += 1
|
165
|
+
return segments
|
166
|
+
|
167
|
+
@staticmethod
|
168
|
+
def __build_url(template, rep_id, bandwidth, segment_time=None, segment_number=None):
|
169
|
+
"""
|
170
|
+
Constrói uma URL substituindo placeholders.
|
171
|
+
|
172
|
+
Args:
|
173
|
+
template (str): Template de URL.
|
174
|
+
rep_id (str): ID da representação.
|
175
|
+
bandwidth (int): Largura de banda.
|
176
|
+
segment_time (int, opcional): Timestamp do segmento.
|
177
|
+
segment_number (int, opcional): Número do segmento.
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
str: URL formatada.
|
181
|
+
"""
|
182
|
+
url = template.replace('$RepresentationID$', rep_id).replace('$Bandwidth$', str(bandwidth))
|
183
|
+
if segment_time is not None:
|
184
|
+
url = url.replace('$Time$', str(segment_time))
|
185
|
+
if segment_number is not None:
|
186
|
+
url = url.replace('$Number$', str(segment_number))
|
187
|
+
return url
|
188
|
+
|
189
|
+
def set_selected_resolution(self, resolution: tuple):
|
190
|
+
"""
|
191
|
+
Define a resolução selecionada para a recuperação de segmentos de vídeo.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
resolution (tuple): Resolução desejada (largura, altura).
|
195
|
+
|
196
|
+
Raises:
|
197
|
+
Exception: Se a resolução não estiver disponível no manifesto.
|
198
|
+
"""
|
199
|
+
if resolution in self._video_representations:
|
200
|
+
self._selected_resolution = resolution
|
201
|
+
else:
|
202
|
+
raise Exception(
|
203
|
+
f'A resolução {resolution} não está disponível!\n\n'
|
204
|
+
f'\t=> Resoluções disponíveis no arquivo: {self.get_all_video_resolutions()}')
|
205
|
+
|
206
|
+
def get_selected_video_init_url(self):
|
207
|
+
"""
|
208
|
+
Retorna o URL de inicialização para a resolução de vídeo selecionada.
|
209
|
+
|
210
|
+
Returns:
|
211
|
+
str: URL de inicialização do vídeo, ou None se não houver resolução selecionada.
|
212
|
+
"""
|
213
|
+
if self._selected_resolution:
|
214
|
+
return self._video_representations[self._selected_resolution].get('init_url')
|
215
|
+
return None
|
216
|
+
|
217
|
+
def get_all_video_resolutions(self):
|
218
|
+
"""
|
219
|
+
Retorna uma lista de todas as resoluções de vídeo disponíveis.
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
list: lista de tuplas com resoluções de vídeo (largura, altura).
|
223
|
+
"""
|
224
|
+
return list(self._video_representations.keys())
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import json
|
2
|
+
import requests
|
3
|
+
from .exeptions import UdemyUserApiExceptions, LoginException
|
4
|
+
|
5
|
+
|
6
|
+
def get_courses_plan(tipe: str) -> list:
|
7
|
+
""" Obtém uma lista de cursos com base no tipo de plano.
|
8
|
+
|
9
|
+
Args: tipe (str): Tipo de plano ('default' ou 'plan'). Returns: list: Lista de cursos. Raises: LoginException: Se
|
10
|
+
a sessão estiver expirada. UdemyUserApiExceptions: Se houver erro ao obter os cursos."""
|
11
|
+
from .api import HEADERS_USER
|
12
|
+
from .authenticate import UdemyAuth
|
13
|
+
auth = UdemyAuth()
|
14
|
+
if not auth.verif_login():
|
15
|
+
raise LoginException("Sessão expirada!")
|
16
|
+
courses_data = []
|
17
|
+
if tipe == 'default':
|
18
|
+
response = requests.get(f"https://www.udemy.com/api-2.0/users/me/subscribed-courses/?page_size=1000"
|
19
|
+
f"&ordering=-last_accessed&fields[course]=image_240x135,title,completion_ratio&"
|
20
|
+
f"is_archived=false",
|
21
|
+
headers=HEADERS_USER)
|
22
|
+
if response.status_code == 200:
|
23
|
+
r = json.loads(response.text)
|
24
|
+
results = r.get("results", None)
|
25
|
+
if results:
|
26
|
+
courses_data.append(results)
|
27
|
+
else:
|
28
|
+
r = json.loads(response.text)
|
29
|
+
raise UdemyUserApiExceptions(f"Error obtain courses 'default' -> {r}")
|
30
|
+
elif tipe == 'plan':
|
31
|
+
response2 = requests.get(
|
32
|
+
url="https://www.udemy.com/api-2.0/users/me/subscription-course-enrollments/?"
|
33
|
+
"fields[course]=@min,visible_instructors,image_240x135,image_480x270,completion_ratio,"
|
34
|
+
"last_accessed_time,enrollment_time,is_practice_test_course,features,num_collections,"
|
35
|
+
"published_title,buyable_object_type,remaining_time,is_assigned,next_to_watch_item,"
|
36
|
+
"is_in_user_subscription&fields[user]=@min&ordering=-last_accessed&page_size=1000&"
|
37
|
+
"max_progress=99.9&fields[lecture]=@min,content_details,asset,url,thumbnail_url,"
|
38
|
+
"last_watched_second,object_index&fields[quiz]=@min,content_details,asset,url,object_index&"
|
39
|
+
"fields[practice]=@min,content_details,asset,estimated_duration,learn_url,object_index",
|
40
|
+
headers=HEADERS_USER)
|
41
|
+
if response2.status_code == 200:
|
42
|
+
r = json.loads(response2.text)
|
43
|
+
results2 = r.get("results", None)
|
44
|
+
if results2:
|
45
|
+
courses_data.append(results2)
|
46
|
+
else:
|
47
|
+
r = json.loads(response2.text)
|
48
|
+
raise UdemyUserApiExceptions(f"Error obtain courses 'plan' -> {r}")
|
49
|
+
else:
|
50
|
+
raise UdemyUserApiExceptions("Atenção dev! os parametros são : 'plan' e 'default'")
|
51
|
+
return courses_data
|
52
|
+
|
53
|
+
|
54
|
+
def get_details_courses(course_id):
|
55
|
+
"""
|
56
|
+
Obtém detalhes de um curso específico.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
course_id (int): ID do curso.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
dict: Dicionário contendo os detalhes do curso.
|
63
|
+
|
64
|
+
Raises:
|
65
|
+
LoginException: Se a sessão estiver expirada.
|
66
|
+
UdemyUserApiExceptions: Se houver erro ao obter os detalhes do curso.
|
67
|
+
"""
|
68
|
+
from .api import HEADERS_USER
|
69
|
+
from .authenticate import UdemyAuth
|
70
|
+
auth = UdemyAuth()
|
71
|
+
if not auth.verif_login():
|
72
|
+
raise LoginException("Sessão expirada!")
|
73
|
+
response = requests.get(
|
74
|
+
f"https://www.udemy.com/api-2.0/courses/{course_id}/subscriber-curriculum-items/?"
|
75
|
+
f"caching_intent=True&fields%5Basset%5D=title%2Cfilename%2Casset_type%2Cstatus%2Ctime_estimation%2"
|
76
|
+
f"Cis_external&fields%5Bchapter%5D=title%2Cobject_index%2Cis_published%2Csort_order&fields%5Blecture"
|
77
|
+
f"%5D=title%2Cobject_index%2Cis_published%2Csort_order%2Ccreated%2Casset%2Csupplementary_assets%2"
|
78
|
+
f"Cis_free&fields%5Bpractice%5D=title%2Cobject_index%2Cis_published%2Csort_order&fields%5Bquiz%5D="
|
79
|
+
f"title%2Cobject_index%2Cis_published%2Csort_order%2Ctype&pages&page_size=400&fields[lecture]=asset,"
|
80
|
+
f"description,download_url,is_free,last_watched_second&fields[asset]=asset_type,length,"
|
81
|
+
f"media_license_token,course_is_drmed,external_url&q=0.3108014137011559",
|
82
|
+
headers=HEADERS_USER)
|
83
|
+
if response.status_code == 200:
|
84
|
+
resposta = json.loads(response.text)
|
85
|
+
return resposta
|
86
|
+
else:
|
87
|
+
raise UdemyUserApiExceptions(f"Erro ao obter detalhes do curso! Código de status: {response.status_code}")
|
88
|
+
|
89
|
+
|
90
|
+
def get_course_infor(course_id):
|
91
|
+
"""
|
92
|
+
Obtém informações de um curso específico.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
course_id (int): ID do curso.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
dict: Dicionário contendo as informações do curso.
|
99
|
+
|
100
|
+
Raises:
|
101
|
+
LoginException: Se a sessão estiver expirada.
|
102
|
+
UdemyUserApiExceptions: Se houver erro ao obter as informações do curso.
|
103
|
+
"""
|
104
|
+
from .api import HEADERS_USER
|
105
|
+
from .authenticate import UdemyAuth
|
106
|
+
auth = UdemyAuth()
|
107
|
+
if not auth.verif_login():
|
108
|
+
raise LoginException("Sessão expirada!")
|
109
|
+
end_point = (
|
110
|
+
f'https://www.udemy.com/api-2.0/courses/{course_id}/?fields[course]=title,context_info,primary_category,'
|
111
|
+
'primary_subcategory,avg_rating_recent,visible_instructors,locale,estimated_content_length,'
|
112
|
+
'num_subscribers')
|
113
|
+
response = requests.get(end_point, headers=HEADERS_USER)
|
114
|
+
if response.status_code == 200:
|
115
|
+
return json.loads(response.text)
|
116
|
+
else:
|
117
|
+
raise UdemyUserApiExceptions("Erro ao obter informações do curso!")
|
udemy_userAPI/udemy.py
ADDED
@@ -0,0 +1,93 @@
|
|
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 usuário da plataforma Udemy"""
|
13
|
+
|
14
|
+
def __init__(self):
|
15
|
+
"""
|
16
|
+
Inicializa o objeto Udemy.
|
17
|
+
|
18
|
+
Raises:
|
19
|
+
LoginException: Se a sessão estiver expirada.
|
20
|
+
"""
|
21
|
+
self.__headers = HEADERS_USER
|
22
|
+
if not verif_login:
|
23
|
+
raise LoginException("Sessão expirada!")
|
24
|
+
|
25
|
+
@staticmethod
|
26
|
+
def my_subscribed_courses_by_plan() -> list[dict]:
|
27
|
+
"""
|
28
|
+
Obtém os cursos que o usuário está inscrito, obtidos através de planos (assinatura).
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
list[dict]: Lista de cursos inscritos através de planos.
|
32
|
+
|
33
|
+
Raises:
|
34
|
+
UdemyUserApiExceptions: Se houver erro ao obter os cursos.
|
35
|
+
"""
|
36
|
+
try:
|
37
|
+
courses = get_courses_plan(tipe='plan')
|
38
|
+
return courses
|
39
|
+
except UdemyUserApiExceptions as e:
|
40
|
+
raise UnhandledExceptions(e)
|
41
|
+
|
42
|
+
@staticmethod
|
43
|
+
def my_subscribed_courses() -> list[dict]:
|
44
|
+
"""
|
45
|
+
Obtém os cursos que o usuário está inscrito, excluindo listas vazias ou nulas.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
list[dict]: Lista de todos os cursos inscritos.
|
49
|
+
|
50
|
+
Raises:
|
51
|
+
UdemyUserApiExceptions: Se houver erro ao obter os cursos.
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
# Obtém os cursos
|
55
|
+
courses1 = get_courses_plan(tipe='default') # lista de cursos padrão
|
56
|
+
courses2 = get_courses_plan(tipe='plan') # lista de cursos de um plano
|
57
|
+
|
58
|
+
# Cria uma lista vazia para armazenar os cursos válidos
|
59
|
+
all_courses = []
|
60
|
+
|
61
|
+
# Adiciona a lista somente se não estiver vazia ou nula
|
62
|
+
if courses1:
|
63
|
+
for i in courses1:
|
64
|
+
all_courses.extend(i)
|
65
|
+
if courses2:
|
66
|
+
for i in courses2:
|
67
|
+
all_courses.extend(i)
|
68
|
+
|
69
|
+
return all_courses
|
70
|
+
|
71
|
+
except UdemyUserApiExceptions as e:
|
72
|
+
raise UnhandledExceptions(e)
|
73
|
+
|
74
|
+
@staticmethod
|
75
|
+
def get_details_course(course_id):
|
76
|
+
"""
|
77
|
+
Obtém detalhes de um curso através do ID.
|
78
|
+
|
79
|
+
Args:
|
80
|
+
course_id: O ID do curso.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
Course: Um objeto Course contendo os detalhes do curso.
|
84
|
+
|
85
|
+
Raises:
|
86
|
+
UnhandledExceptions: Se houver erro ao obter os detalhes do curso.
|
87
|
+
"""
|
88
|
+
try:
|
89
|
+
d = get_details_courses(course_id)
|
90
|
+
b = Course(course_id=course_id, results=d)
|
91
|
+
return b
|
92
|
+
except UnhandledExceptions as e:
|
93
|
+
raise UnhandledExceptions(e)
|
@@ -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.3.2
|
4
|
+
Summary: Obtenha detalhes de cursos que o usuário esteja inscrito da plataforma Udemy,usando o EndPoint de usuário o mesmo que o navegador utiliza para acessar e redenrizar os cursos.
|
5
|
+
Author: PauloCesar-dev404
|
6
|
+
Author-email: paulocesar0073dev404@gmail.com
|
7
|
+
License: MIT
|
8
|
+
Project-URL: GitHub, https://github.com/PauloCesar-dev404/udemy-userAPI
|
9
|
+
Project-URL: Bugs/Melhorias, https://github.com/PauloCesar-dev404/udemy-userAPI/issues
|
10
|
+
Project-URL: Documentação, https://github.com/PauloCesar-dev404/udemy-userAPI/wiki
|
11
|
+
Keywords: udemy,udemy python,pyudemy,udemy_userAPI,udemy api
|
12
|
+
Platform: any
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
License-File: LICENSE
|
15
|
+
Requires-Dist: requests
|
16
|
+
Requires-Dist: cloudscraper
|
17
|
+
Requires-Dist: pywidevine
|
18
|
+
|
19
|
+
# udemy-userAPI
|
20
|
+
|
21
|
+
|
22
|
+

|
23
|
+

|
24
|
+
[](https://paulocesar-dev404.github.io/me-apoiando-online/)
|
25
|
+
[](https://github.com/PauloCesar-dev404/udemy-userAPI/blob/main/docs/iniciando.md)
|
26
|
+
|
27
|
+
|
28
|
+
Obtenha detalhes de cursos da plataforma udemy com a api de usuário,usando esta lib
|
29
|
+
|
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,29 @@
|
|
1
|
+
animation_consoles/__init__.py,sha256=5uHhe-PVZ54FHWxbF1sNvNt4fuQf3FtZWVo2Mjo11a8,40
|
2
|
+
animation_consoles/animation.py,sha256=ZreNtdD0HYeqlRx-f1d1twUU4sOFTR7vJ3S6tMQpnHM,2122
|
3
|
+
ffmpeg_for_python/__config__.py,sha256=nCPrYs1NkMnyfyg5ITw9wOar4nUJOxwONrItVpVBVBM,4719
|
4
|
+
ffmpeg_for_python/__init__.py,sha256=-BMtoX8Yof_pnHra2OzoV3faxMubpMvUedMy8TqI8dc,214
|
5
|
+
ffmpeg_for_python/__utils.py,sha256=Qy3J5f4lOIPcSNbTwiawfiHjYPdZ_tq7hafStnnqwA4,3263
|
6
|
+
ffmpeg_for_python/__version__.py,sha256=HLFuN4n_leeJE5twr7yH2AAFyfIcEHzxElLRP1FUKmQ,422
|
7
|
+
ffmpeg_for_python/exeptions.py,sha256=tg-TBdaq_NHxZOCAhkMttzwtJVILPAQPLOKqofe5PPA,3627
|
8
|
+
ffmpeg_for_python/ffmpeg.py,sha256=G2VGHOIhErsqQI4OVlUnIQGmleNCjxyFqzNAMNnoD6I,7920
|
9
|
+
m3u8_analyzer/M3u8Analyzer.py,sha256=aUgxk2jS84MFDNbjlOT8FRiJerFI_jGcKMu9uv1EwcE,36620
|
10
|
+
m3u8_analyzer/__init__.py,sha256=v7CiVqsCq2YH347C-QR1kHPJtXFFdru8qole3E9adCY,217
|
11
|
+
m3u8_analyzer/__version__.py,sha256=YP3yT87ZKrU3eARUUdQ_pg4xAXLGfBXjH4ZgEoZSq1I,25
|
12
|
+
m3u8_analyzer/exeptions.py,sha256=fK6bU3YxNSbfsPmCp4yudUvmwy_g6dj2KwIkH0dW4LI,3672
|
13
|
+
udemy_userAPI/__init__.py,sha256=BPle89xE_CMTKKe_Lw6jioYLgpH-q_Lpho2S-n1PIUA,206
|
14
|
+
udemy_userAPI/__version__.py,sha256=aMKbixxAkks_VfsbQJs0sU6jFllEvVm4k3e-E32e5u8,405
|
15
|
+
udemy_userAPI/api.py,sha256=oDVylMQ6CsMeJ7V7FKdjjjvY0GrHHqJdVMTTptQmSiE,24277
|
16
|
+
udemy_userAPI/authenticate.py,sha256=gpHwS34WboQCpktQg6NsvLBJzX9AL8Do3Gk08PLR4GY,14133
|
17
|
+
udemy_userAPI/bultins.py,sha256=ZEksThVSaDe7jsyeCqhCiFLJsy-pIN8EdnJ6Aoh4s9k,16428
|
18
|
+
udemy_userAPI/exeptions.py,sha256=kfnPdZpqYY8nd0gnl6_Vh-MIz-XupmmbRPIuFnyXupk,692
|
19
|
+
udemy_userAPI/sections.py,sha256=oP3jvbsWocemqhzzOAOoeL7ICF1f4gNvjL4FJBt47pE,5474
|
20
|
+
udemy_userAPI/udemy.py,sha256=SpK0LI4hjO45nZDz5waw-Py-d0uulBb28TVjltyWBxM,2920
|
21
|
+
udemy_userAPI/.cache/.udemy_userAPI,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
+
udemy_userAPI/mpd_analyzer/__init__.py,sha256=i3JVWyvcFLaj5kPmx8c1PgjsLht7OUIQQClD4yqYbo8,102
|
23
|
+
udemy_userAPI/mpd_analyzer/bin.wvd,sha256=1rAJdCc120hQlX9qe5KUS628eY2ZHYxQSmyhGNefSzo,2956
|
24
|
+
udemy_userAPI/mpd_analyzer/mpd_parser.py,sha256=PgUkHc5x8FTuXFCuYkWPZr9TaO_nsKalb02EFYl_zeA,8926
|
25
|
+
udemy_userAPI-0.3.2.dist-info/LICENSE,sha256=l4jdKYt8gSdDFOGr09vCKnMn_Im55XIcQKqTDEtFfNs,1095
|
26
|
+
udemy_userAPI-0.3.2.dist-info/METADATA,sha256=ir_5qB1Q1qu66M3pCaL9VN7BUZBX0MwStZg0iQ0r03k,1438
|
27
|
+
udemy_userAPI-0.3.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
28
|
+
udemy_userAPI-0.3.2.dist-info/top_level.txt,sha256=ijTINaSDRKhdahY_X7dmSRFTxBIwQErWv9ATCG55mog,14
|
29
|
+
udemy_userAPI-0.3.2.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
udemy_userAPI
|