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 options VidArchive sets itself, so a custom flag must not
17// pass them or a conflicting inverse. The value describes the option for the
18// 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 guard against accidental misuse only. This is not a security boundary:
32 // yt-dlp accepts unambiguous prefixes like --exec-b, --alias can define new
33 // options, and --ffmpeg-location and --plugin-dirs are not listed at all.
34 // Custom flags are operator-controlled by design.
35 "--exec": "running external commands (not permitted)",
36 "--exec-before-download": "running external commands (not permitted)",
37 "--postprocessor-args": "post-processor arguments (not permitted)",
38 "--ppa": "post-processor arguments (not permitted)",
39 "--downloader": "selecting an external downloader (not permitted)",
40 "--external-downloader": "selecting an external downloader (not permitted)",
41 "--downloader-args": "external downloader arguments (not permitted)",
42 "--external-downloader-args": "external downloader arguments (not permitted)",
43}
44
45// reservedSubscriptionFlags are additionally reserved for subscription runs,
46// where VidArchive drives info-json writing and the refresh mode.
47var reservedSubscriptionFlags = map[string]string{
48 "--write-info-json": "info-json writing (needed to track item identity)",
49 "--no-write-info-json": "info-json writing (needed to track item identity)",
50 "--download-archive": "the download archive (managed by Skip mode)",
51 "--no-download-archive": "the download archive (managed by Skip mode)",
52 "--skip-download": "media downloading (managed by Metadata mode)",
53 "--no-skip-download": "media downloading (managed by Metadata mode)",
54}
55
56// checkReservedFlags rejects custom flags that clash with options VidArchive
57// controls, naming the offender. It matches both "--flag" and "--flag=value".
58func checkReservedFlags(customFlags string, isSubscription bool) error {
59 tokens, err := splitFlags(customFlags)
60 if err != nil {
61 return fmt.Errorf("invalid custom flags: %w", err)
62 }
63 for _, tok := range tokens {
64 name, _, _ := strings.Cut(tok, "=")
65
66 desc, ok := reservedFlags[name]
67 if !ok && isSubscription {
68 desc, ok = reservedSubscriptionFlags[name]
69 }
70 if ok {
71 return fmt.Errorf("custom flag %q conflicts with VidArchive's handling of %s; remove it and try again", tok, desc)
72 }
73 }
74 return nil
75}
76