1
1
import logging
2
2
from collections import defaultdict
3
- from http .cookiejar import MozillaCookieJar
4
3
from pathlib import Path
5
- import re
6
4
from typing import Union
7
5
8
6
import markdownify
13
11
from kodekloud_downloader .helpers import (
14
12
download_all_pdf ,
15
13
download_video ,
16
- get_video_info ,
17
14
is_normal_content ,
18
15
normalize_name ,
16
+ parse_token ,
19
17
)
18
+ from kodekloud_downloader .models .course import CourseDetail
20
19
from kodekloud_downloader .models .courses import Course
21
20
from kodekloud_downloader .models .helper import fetch_course_detail
22
- from kodekloud_downloader .models2 import Quiz , Topic
21
+ from kodekloud_downloader .models . quiz import Quiz
23
22
24
23
logger = logging .getLogger (__name__ )
25
24
26
25
27
- def download_quiz (output_dir : str , sep : bool ):
26
+ def download_quiz (output_dir : str , sep : bool ) -> None :
27
+ """
28
+ Download quizzes from the API and save them as Markdown files.
29
+
30
+ :param output_dir: The directory path where the Markdown files will be saved.
31
+ :param sep: A boolean flag indicating whether to separate each quiz into individual files.
32
+ If `True`, each quiz will be saved as a separate Markdown file. If `False`,
33
+ all quizzes will be combined into a single Markdown file.
34
+ :return: None
35
+ :raises ValueError: If `output_dir` is not a valid directory path.
36
+ :raises requests.RequestException: For errors related to the HTTP request.
37
+ :raises IOError: For file I/O errors.
38
+ """
28
39
quiz_markdown = [] if sep else ["# KodeKloud Quiz" ]
29
40
response = requests .get ("https://mcq-backend-main.kodekloud.com/api/quizzes/all" )
30
41
response .raise_for_status ()
@@ -75,21 +86,21 @@ def download_quiz(output_dir: str, sep: bool):
75
86
print (f"Quiz file written in { output_file } " )
76
87
77
88
78
- def parseToken ( cookiefile ) :
79
- """Parse a cookies.txt file and return a dictionary of key value pairs
80
- compatible with requests."""
89
+ def parse_course_from_url ( url : str ) -> CourseDetail :
90
+ """
91
+ Parse the course slug from the given URL and fetch the course details.
81
92
82
- cookies = {}
83
- with open ( cookiefile , "r" ) as fp :
84
- for line in fp :
85
- if line . strip () and not re . match ( r"^\#" , line ):
86
- lineFields = line .strip (). split ( " \t " )
87
- cookies [ lineFields [ 5 ]] = lineFields [ 6 ]
88
- return cookies . get ( "session-cookie" )
93
+ :param url: The URL from which to extract the course slug.
94
+ :return: An instance of `CourseDetail` containing the course details.
95
+ :raises ValueError: If the URL does not contain a valid course slug.
96
+ """
97
+ url = url .strip ("/ " )
98
+ course_slug = url . split ( "/" )[ - 1 ]
99
+ return fetch_course_detail ( course_slug )
89
100
90
101
91
102
def download_course (
92
- course : Course ,
103
+ course : Union [ Course , CourseDetail ] ,
93
104
cookie : str ,
94
105
quality : str ,
95
106
output_dir : Union [str , Path ],
@@ -98,22 +109,22 @@ def download_course(
98
109
"""
99
110
Download a course from KodeKloud.
100
111
101
- :param url : The course URL
112
+ :param course : The Course or CourseDetail object
102
113
:param cookie: The user's authentication cookie
103
114
:param quality: The video quality (e.g. "720p")
104
115
:param output_dir: The output directory for the downloaded course
105
116
:param max_duplicate_count: Maximum duplicate video before after cookie expire message will be raised
106
117
"""
107
118
session = requests .Session ()
108
- cj = MozillaCookieJar (cookie )
109
- cj .load (ignore_discard = True , ignore_expires = True )
110
- session_token = parseToken (cookie )
119
+ session_token = parse_token (cookie )
111
120
headers = {"authorization" : f"bearer { session_token } " }
112
121
params = {
113
122
"course_id" : course .id ,
114
123
}
115
124
116
- course_detail = fetch_course_detail (course .slug )
125
+ course_detail = (
126
+ fetch_course_detail (course .slug ) if isinstance (course , Course ) else course
127
+ )
117
128
118
129
downloaded_videos = defaultdict (int )
119
130
for module_index , module in enumerate (course_detail .modules , start = 1 ):
@@ -133,7 +144,11 @@ def download_course(
133
144
response = session .get (url , headers = headers , params = params )
134
145
response .raise_for_status ()
135
146
lesson_video_url = response .json ()["video_url" ]
136
- current_video_url = f"https://player.vimeo.com/video/{ lesson_video_url .split ('/' )[- 1 ]} "
147
+ # TODO: Maybe if in future KodeKloud change the video streaming service, this area will need some working.
148
+ # Try to generalize this for future enhacement?
149
+ current_video_url = (
150
+ f"https://player.vimeo.com/video/{ lesson_video_url .split ('/' )[- 1 ]} "
151
+ )
137
152
if (
138
153
current_video_url in downloaded_videos
139
154
and downloaded_videos [current_video_url ] > max_duplicate_count
@@ -178,11 +193,13 @@ def create_file_path(
178
193
)
179
194
180
195
181
- def download_video_lesson (lesson_video_url , file_path : Path , cookie : str , quality : str ) -> None :
196
+ def download_video_lesson (
197
+ lesson_video_url , file_path : Path , cookie : str , quality : str
198
+ ) -> None :
182
199
"""
183
200
Download a video lesson.
184
201
185
- :param lesson : The lesson object
202
+ :param lesson_video_url : The lesson video URL
186
203
:param file_path: The output file path for the video
187
204
:param cookie: The user's authentication cookie
188
205
:param quality: The video quality (e.g. "720p")
@@ -212,10 +229,11 @@ def download_resource_lesson(lesson_url, file_path: Path, cookie: str) -> None:
212
229
"""
213
230
Download a resource lesson.
214
231
215
- :param lesson : The lesson object
232
+ :param lesson_url : The lesson url
216
233
:param file_path: The output file path for the resource
217
234
:param cookie: The user's authentication cookie
218
235
"""
236
+ # TODO: Did we break this? I have no idea.
219
237
page = requests .get (lesson_url )
220
238
soup = BeautifulSoup (page .content , "html.parser" )
221
239
content = soup .find ("div" , class_ = "learndash_content_wrap" )
0 commit comments