From 8591ae283ee2ea62f43e72ba5f55383b94a97aed Mon Sep 17 00:00:00 2001 From: joren Date: Wed, 11 Mar 2026 19:36:54 +0100 Subject: [PATCH 1/3] Add support to download all the users courses --- cmd/canvasarchiver/main.go | 25 +++++++++++++++++++++++-- internal/canvas/client.go | 14 ++++++++++++++ internal/models/models.go | 1 + 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/cmd/canvasarchiver/main.go b/cmd/canvasarchiver/main.go index 8749031..d2ce549 100644 --- a/cmd/canvasarchiver/main.go +++ b/cmd/canvasarchiver/main.go @@ -14,6 +14,7 @@ import ( func main() { filesOnly := flag.Bool("fo", false, "Files only mode - download all files to a single directory without module structure") + me := flag.Bool("me", false, "Download all enrolled courses") flag.Parse() httpClient := &http.Client{} @@ -25,11 +26,31 @@ func main() { return } + if *me { + canvasClient := canvas.NewClient(httpClient, accessToken, "", *filesOnly) + courses, err := canvasClient.GetEnrolledCourses() + if err != nil { + fmt.Printf("Error fetching courses: %v\n", err) + return + } + + fmt.Printf("[+] Found %d enrolled courses\n", len(courses)) + for _, course := range courses { + fmt.Printf(" -> Downloading: %s (ID: %d)\n", course.Name, course.ID) + downloadCourse(httpClient, accessToken, fmt.Sprintf("%d", course.ID), *filesOnly) + } + return + } + var courseID string fmt.Print("Enter Course ID: ") fmt.Scanln(&courseID) - canvasClient := canvas.NewClient(httpClient, accessToken, courseID, *filesOnly) + downloadCourse(httpClient, accessToken, courseID, *filesOnly) +} + +func downloadCourse(httpClient *http.Client, accessToken, courseID string, filesOnly bool) { + canvasClient := canvas.NewClient(httpClient, accessToken, courseID, filesOnly) if err := canvasClient.GetCourseInfo(); err != nil { fmt.Printf("Error: %v\n", err) @@ -44,7 +65,7 @@ func main() { canvasClient.DownloadModules(courseRoot) - if !*filesOnly { + if !filesOnly { panopto.DownloadMainRecordings(httpClient, accessToken, courseID, courseRoot) } } diff --git a/internal/canvas/client.go b/internal/canvas/client.go index 9e9beb1..ad2b1c2 100644 --- a/internal/canvas/client.go +++ b/internal/canvas/client.go @@ -51,6 +51,20 @@ func (c *Client) GetCourseInfo() error { return nil } +func (c *Client) GetEnrolledCourses() ([]models.Course, error) { + req, _ := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/courses?enrollment_state=active&per_page=100", config.BaseURL), nil) + req.Header.Set("Authorization", "Bearer "+c.AccessToken) + resp, err := c.HTTPClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var courses []models.Course + json.NewDecoder(resp.Body).Decode(&courses) + return courses, nil +} + func (c *Client) DownloadCourseFiles(root string) { fmt.Println("\n[*] Fetching regular course files...") diff --git a/internal/models/models.go b/internal/models/models.go index 68e8589..14fdadd 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -10,6 +10,7 @@ type TokenResponse struct { } type Course struct { + ID int `json:"id"` Name string `json:"name"` } -- 2.49.1 From b8e6180b35a9e0a325a93cff7c5af6e0a574cc4a Mon Sep 17 00:00:00 2001 From: joren Date: Wed, 11 Mar 2026 19:56:37 +0100 Subject: [PATCH 2/3] add numbering --- cmd/canvasarchiver/main.go | 11 ++++++----- internal/canvas/client.go | 15 +++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/cmd/canvasarchiver/main.go b/cmd/canvasarchiver/main.go index d2ce549..18b3c87 100644 --- a/cmd/canvasarchiver/main.go +++ b/cmd/canvasarchiver/main.go @@ -15,6 +15,7 @@ import ( func main() { filesOnly := flag.Bool("fo", false, "Files only mode - download all files to a single directory without module structure") me := flag.Bool("me", false, "Download all enrolled courses") + moduleNumbers := flag.Bool("n", false, "Prefix modules with order numbers [1], [2], etc.") flag.Parse() httpClient := &http.Client{} @@ -27,7 +28,7 @@ func main() { } if *me { - canvasClient := canvas.NewClient(httpClient, accessToken, "", *filesOnly) + canvasClient := canvas.NewClient(httpClient, accessToken, "", *filesOnly, *moduleNumbers) courses, err := canvasClient.GetEnrolledCourses() if err != nil { fmt.Printf("Error fetching courses: %v\n", err) @@ -37,7 +38,7 @@ func main() { fmt.Printf("[+] Found %d enrolled courses\n", len(courses)) for _, course := range courses { fmt.Printf(" -> Downloading: %s (ID: %d)\n", course.Name, course.ID) - downloadCourse(httpClient, accessToken, fmt.Sprintf("%d", course.ID), *filesOnly) + downloadCourse(httpClient, accessToken, fmt.Sprintf("%d", course.ID), *filesOnly, *moduleNumbers) } return } @@ -46,11 +47,11 @@ func main() { fmt.Print("Enter Course ID: ") fmt.Scanln(&courseID) - downloadCourse(httpClient, accessToken, courseID, *filesOnly) + downloadCourse(httpClient, accessToken, courseID, *filesOnly, *moduleNumbers) } -func downloadCourse(httpClient *http.Client, accessToken, courseID string, filesOnly bool) { - canvasClient := canvas.NewClient(httpClient, accessToken, courseID, filesOnly) +func downloadCourse(httpClient *http.Client, accessToken, courseID string, filesOnly, moduleNumbers bool) { + canvasClient := canvas.NewClient(httpClient, accessToken, courseID, filesOnly, moduleNumbers) if err := canvasClient.GetCourseInfo(); err != nil { fmt.Printf("Error: %v\n", err) diff --git a/internal/canvas/client.go b/internal/canvas/client.go index ad2b1c2..e0fe885 100644 --- a/internal/canvas/client.go +++ b/internal/canvas/client.go @@ -22,15 +22,17 @@ type Client struct { CourseID string CourseName string FilesOnly bool + ModuleNumbers bool downloadedFiles map[string]bool } -func NewClient(httpClient *http.Client, accessToken, courseID string, filesOnly bool) *Client { +func NewClient(httpClient *http.Client, accessToken, courseID string, filesOnly, moduleNumbers bool) *Client { return &Client{ HTTPClient: httpClient, AccessToken: accessToken, CourseID: courseID, FilesOnly: filesOnly, + ModuleNumbers: moduleNumbers, downloadedFiles: make(map[string]bool), } } @@ -152,15 +154,20 @@ func (c *Client) DownloadModules(courseRoot string) { json.NewDecoder(resp.Body).Decode(&modules) resp.Body.Close() - for _, mod := range modules { + for i, mod := range modules { + modName := mod.Name + if c.ModuleNumbers { + modName = fmt.Sprintf("[%d] %s", i+1, mod.Name) + } + modBaseDir := courseRoot if !c.FilesOnly { - modBaseDir = filepath.Join(courseRoot, "Modules", utils.Sanitize(mod.Name)) + modBaseDir = filepath.Join(courseRoot, "Modules", utils.Sanitize(modName)) } os.MkdirAll(modBaseDir, 0o755) if !c.FilesOnly { - fmt.Printf("\n[Module] %s\n", mod.Name) + fmt.Printf("\n[Module] %s\n", modName) } subHeaderStack := []string{} -- 2.49.1 From 9691ecd7a551cc3d03bbc97b62df86ad8e5bba38 Mon Sep 17 00:00:00 2001 From: joren Date: Wed, 11 Mar 2026 20:06:46 +0100 Subject: [PATCH 3/3] Fix course files --- internal/canvas/client.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/canvas/client.go b/internal/canvas/client.go index e0fe885..19d443a 100644 --- a/internal/canvas/client.go +++ b/internal/canvas/client.go @@ -112,7 +112,11 @@ func (c *Client) DownloadCourseFiles(root string) { subDir := root if !c.FilesOnly { - subDir = filepath.Join(root, "Course Files", safeFolderPath) + if safeFolderPath != "" && strings.ToLower(safeFolderPath) != "course files" { + subDir = filepath.Join(root, "Course Files", safeFolderPath) + } else { + subDir = filepath.Join(root, "Course Files") + } } os.MkdirAll(subDir, 0o755) path := filepath.Join(subDir, utils.Sanitize(file.DisplayName)) -- 2.49.1