ytdlp_flags.go
⎇
Raw
1package service
2
3import (
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.
12func 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.
19var 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.
48var 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".
59func 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