udemy-userAPI 0.3.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|