ytdlp_flags.go
| 1 | package service |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "strings" |
| 6 | |
| 7 | shellwords "github.com/mattn/go-shellwords" |
| 8 | ) |
| 9 | |
| 10 | // splitFlags splits a custom-flags string like a POSIX shell would, so quoted |
| 11 | // values such as --match-filter "duration > 60" stay one argument. |
| 12 | func splitFlags(s string) ([]string, error) { |
| 13 | return shellwords.Parse(s) |
| 14 | } |
| 15 | |
| 16 | // reservedFlags are yt-dlp options VidArchive always sets itself; user custom |
| 17 | // flags must not pass them (or a conflicting inverse). The value describes what |
| 18 | // the option controls, for the failure message. |
| 19 | var reservedFlags = map[string]string{ |
| 20 | "-o": "the output template", |
| 21 | "--output": "the output template", |
| 22 | "-P": "the download path", |
| 23 | "--paths": "the download path", |
| 24 | "--cookies": "cookies (set these in Settings instead)", |
| 25 | "--no-cookies": "cookies (set these in Settings instead)", |
| 26 | "--newline": "progress output formatting (VidArchive sets this to stream logs)", |
| 27 | |
| 28 | "--write-playlist-metafiles": "playlist metadata files (VidArchive imports per-item metadata only)", |
| 29 | "--no-write-playlist-metafiles": "playlist metadata files (VidArchive imports per-item metadata only)", |
| 30 | |
| 31 | // These options are blocked as a guard against accidental misuse. The list is |
| 32 | // not a security boundary: yt-dlp accepts unambiguous option prefixes (e.g. |
| 33 | // --exec-b) and --alias can define new options, and options such as |
| 34 | // --ffmpeg-location or --plugin-dirs are not listed. Custom flags are |
| 35 | // operator-controlled by design. |
| 36 | "--exec": "running external commands (not permitted)", |
| 37 | "--exec-before-download": "running external commands (not permitted)", |
| 38 | "--postprocessor-args": "post-processor arguments (not permitted)", |
| 39 | "--ppa": "post-processor arguments (not permitted)", |
| 40 | "--downloader": "selecting an external downloader (not permitted)", |
| 41 | "--external-downloader": "selecting an external downloader (not permitted)", |
| 42 | "--downloader-args": "external downloader arguments (not permitted)", |
| 43 | "--external-downloader-args": "external downloader arguments (not permitted)", |
| 44 | } |
| 45 | |
| 46 | // reservedSubscriptionFlags are additionally reserved for subscription runs, |
| 47 | // where VidArchive drives info-json writing and the refresh mode. |
| 48 | var reservedSubscriptionFlags = map[string]string{ |
| 49 | "--write-info-json": "info-json writing (needed to track item identity)", |
| 50 | "--no-write-info-json": "info-json writing (needed to track item identity)", |
| 51 | "--download-archive": "the download archive (managed by Skip mode)", |
| 52 | "--no-download-archive": "the download archive (managed by Skip mode)", |
| 53 | "--skip-download": "media downloading (managed by Metadata mode)", |
| 54 | "--no-skip-download": "media downloading (managed by Metadata mode)", |
| 55 | } |
| 56 | |
| 57 | // checkReservedFlags rejects custom flags that clash with options VidArchive |
| 58 | // controls, naming the offender. It matches both "--flag" and "--flag=value". |
| 59 | func checkReservedFlags(customFlags string, isSubscription bool) error { |
| 60 | tokens, err := splitFlags(customFlags) |
| 61 | if err != nil { |
| 62 | return fmt.Errorf("invalid custom flags: %w", err) |
| 63 | } |
| 64 | for _, tok := range tokens { |
| 65 | // Both "--flag value" and "--flag=value" name the same option. |
| 66 | name, _, _ := strings.Cut(tok, "=") |
| 67 | |
| 68 | desc, ok := reservedFlags[name] |
| 69 | if !ok && isSubscription { |
| 70 | desc, ok = reservedSubscriptionFlags[name] |
| 71 | } |
| 72 | if ok { |
| 73 | return fmt.Errorf("custom flag %q conflicts with VidArchive's handling of %s; remove it and try again", tok, desc) |
| 74 | } |
| 75 | } |
| 76 | return nil |
| 77 | } |
| 78 |