fix: normalize List.aspx?folderID= to fragment form for yt-dlp

Canvas stores folder links as List.aspx?folderID=X (query param).
yt-dlp's PanoptoList extractor requires List.aspx#folderID="X"
(fragment with quoted ID) to scope the download to that folder.
Without the fragment form it downloaded the entire Panopto instance
(1806 items instead of 3).

Also drop --no-playlist for list URLs since they are intentional
playlists, and use title/%(title)s.%(ext)s output template for them.
This commit is contained in:
2026-05-16 19:27:36 +02:00
parent 13063c6cc5
commit 43392a4132

View File

@@ -17,6 +17,27 @@ import (
"git.directme.in/Joren/CanvasArchiver/internal/utils" "git.directme.in/Joren/CanvasArchiver/internal/utils"
) )
// normalizePanoptoURL converts the query-param form that Canvas stores
// (List.aspx?folderID=X) to the fragment form that yt-dlp's PanoptoList
// extractor understands (List.aspx#folderID="X"). Without this yt-dlp
// ignores the folder filter and downloads the entire Panopto instance.
func normalizePanoptoURL(rawURL string) string {
parsed, err := url.Parse(rawURL)
if err != nil {
return rawURL
}
if strings.Contains(parsed.Path, "List.aspx") {
folderID := parsed.Query().Get("folderID")
if folderID != "" {
// Strip query, set fragment: List.aspx#folderID="<id>"
parsed.RawQuery = ""
parsed.Fragment = fmt.Sprintf(`folderID="%s"`, folderID)
return parsed.String()
}
}
return rawURL
}
func getYoutubeDLCommand() string { func getYoutubeDLCommand() string {
exePath, err := os.Executable() exePath, err := os.Executable()
if err == nil { if err == nil {
@@ -192,13 +213,35 @@ func DownloadVideo(httpClient *http.Client, accessToken, courseID, modDir, input
fmt.Printf(" [*] Downloading video: %s\n", title) fmt.Printf(" [*] Downloading video: %s\n", title)
ytCmd := getYoutubeDLCommand() ytCmd := getYoutubeDLCommand()
cmd := exec.Command(ytCmd,
// Normalize folder URLs so yt-dlp scopes to the right folder.
normalizedURL := normalizePanoptoURL(targetURL)
// Folder/list URLs are intentional playlists; don't pass --no-playlist.
isList := strings.Contains(normalizedURL, "List.aspx")
var outputTpl string
var args []string
if isList {
outputTpl = utils.Sanitize(title) + "/%(title)s.%(ext)s"
args = []string{
"--cookies", cookieFile,
"--referer", config.BaseURL + "/",
"-P", modDir,
"-o", outputTpl,
normalizedURL,
}
} else {
outputTpl = utils.Sanitize(title) + ".%(ext)s"
args = []string{
"--no-playlist", "--no-playlist",
"--cookies", cookieFile, "--cookies", cookieFile,
"--referer", config.BaseURL + "/", "--referer", config.BaseURL + "/",
"-P", modDir, "-P", modDir,
"-o", utils.Sanitize(title)+".%(ext)s", "-o", outputTpl,
targetURL) normalizedURL,
}
}
cmd := exec.Command(ytCmd, args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr