diff --git a/cmd/canvasarchiver/main.go b/cmd/canvasarchiver/main.go index 8749031..18b3c87 100644 --- a/cmd/canvasarchiver/main.go +++ b/cmd/canvasarchiver/main.go @@ -14,6 +14,8 @@ 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{} @@ -25,11 +27,31 @@ func main() { return } + if *me { + canvasClient := canvas.NewClient(httpClient, accessToken, "", *filesOnly, *moduleNumbers) + 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, *moduleNumbers) + } + return + } + var courseID string fmt.Print("Enter Course ID: ") fmt.Scanln(&courseID) - canvasClient := canvas.NewClient(httpClient, accessToken, courseID, *filesOnly) + downloadCourse(httpClient, accessToken, courseID, *filesOnly, *moduleNumbers) +} + +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) @@ -44,7 +66,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..19d443a 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), } } @@ -51,6 +53,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...") @@ -96,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)) @@ -138,15 +158,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{} 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"` }