diff options
Diffstat (limited to 'internal/feed')
| -rw-r--r-- | internal/feed/feed.go | 40 | ||||
| -rw-r--r-- | internal/feed/feed_test.go | 6 |
2 files changed, 37 insertions, 9 deletions
diff --git a/internal/feed/feed.go b/internal/feed/feed.go index 02a016e..642d8f3 100644 --- a/internal/feed/feed.go +++ b/internal/feed/feed.go @@ -28,6 +28,13 @@ type Options struct { // successively later dates so that episode order is unambiguous; see // pubDate handling in Build. Epoch time.Time + // Token, if set, is appended to every enclosure URL. + // + // This is not optional bookkeeping: a podcast client fetches an enclosure as + // a plain GET with nothing carried over from the feed request. If the server + // requires a token and the enclosure URLs lack one, the feed loads, shows + // every episode, and no episode will ever download. + Token string } // Item is one episode. @@ -106,6 +113,22 @@ var audioTypes = map[string]string{ ".wav": "audio/wav", } +// IsAudio reports whether a filename is one podkit treats as an episode. +// +// Exported because the server and the uploader need exactly the same answer as +// the feed generator: a file the server accepts but the generator ignores would +// upload successfully and then never appear in the feed. +func IsAudio(filename string) bool { + _, ok := audioTypes[strings.ToLower(filepath.Ext(filename))] + return ok +} + +// MIMEType returns the enclosure content type for a filename, or "" if it is +// not audio podkit recognises. +func MIMEType(filename string) string { + return audioTypes[strings.ToLower(filepath.Ext(filename))] +} + // ParseName splits "03 - Great Conservation Principles.mp3" into its track // number and title. Files without a numeric prefix keep their whole stem as the // title and report track 0. @@ -126,7 +149,9 @@ func ParseName(filename string) (track int, title string) { // contain spaces and ampersands, and pasting them raw produces both an invalid // URL and invalid XML. Only the final path segment is escaped — escaping the // whole base would mangle its scheme and host. -func EnclosureURL(baseURL, filename string) (string, error) { +// A token, when supplied, is carried on the URL, because that is the only thing +// a podcast client will send back. +func EnclosureURL(baseURL, filename, token string) (string, error) { base, err := url.Parse(baseURL) if err != nil { return "", fmt.Errorf("parsing base URL %q: %w", baseURL, err) @@ -135,6 +160,11 @@ func EnclosureURL(baseURL, filename string) (string, error) { return "", fmt.Errorf("base URL %q needs a scheme and host", baseURL) } base.Path = path.Join(base.Path, filename) + if token != "" { + q := base.Query() + q.Set("k", token) + base.RawQuery = q.Encode() + } // url.URL.String() escapes the path for us; RawPath stays unset so the // escaping is derived from Path. return base.String(), nil @@ -167,11 +197,9 @@ func Build(opts Options, readTags TagReader) (*RSS, error) { if e.IsDir() { continue } - mime, isAudio := audioTypes[strings.ToLower(filepath.Ext(e.Name()))] - if !isAudio { + if !IsAudio(e.Name()) { continue } - _ = mime info, err := e.Info() if err != nil { // A file that vanished mid-scan is not a reason to abandon the @@ -209,7 +237,7 @@ func Build(opts Options, readTags TagReader) (*RSS, error) { items := make([]Item, 0, len(found)) for i, c := range found { - encURL, err := EnclosureURL(opts.BaseURL, c.name) + encURL, err := EnclosureURL(opts.BaseURL, c.name, opts.Token) if err != nil { return nil, err } @@ -229,7 +257,7 @@ func Build(opts Options, readTags TagReader) (*RSS, error) { PubDate: pub.Format(time.RFC1123Z), Enclosure: Enclosure{ URL: encURL, - Type: audioTypes[strings.ToLower(filepath.Ext(c.name))], + Type: MIMEType(c.name), Length: c.size, }, Episode: episode, diff --git a/internal/feed/feed_test.go b/internal/feed/feed_test.go index a529e6f..588c652 100644 --- a/internal/feed/feed_test.go +++ b/internal/feed/feed_test.go @@ -66,7 +66,7 @@ func TestParseName(t *testing.T) { // enclosure URL unescaped produces both an invalid URL and invalid XML — the // single most likely way a hand-rolled feed fails to load at all. func TestEnclosureURL_EscapesFilenames(t *testing.T) { - got, err := EnclosureURL("https://example.com/audio/", "10 - Probability & Uncertainty.mp3") + got, err := EnclosureURL("https://example.com/audio/", "10 - Probability & Uncertainty.mp3", "") if err != nil { t.Fatalf("EnclosureURL: %v", err) } @@ -85,7 +85,7 @@ func TestEnclosureURL_EscapesFilenames(t *testing.T) { } func TestEnclosureURL_HandlesMissingTrailingSlash(t *testing.T) { - got, err := EnclosureURL("https://example.com/audio", "01 - One.mp3") + got, err := EnclosureURL("https://example.com/audio", "01 - One.mp3", "") if err != nil { t.Fatalf("EnclosureURL: %v", err) } @@ -96,7 +96,7 @@ func TestEnclosureURL_HandlesMissingTrailingSlash(t *testing.T) { func TestEnclosureURL_RejectsBadBase(t *testing.T) { for _, bad := range []string{"", "not-a-url", "/just/a/path"} { - if _, err := EnclosureURL(bad, "a.mp3"); err == nil { + if _, err := EnclosureURL(bad, "a.mp3", ""); err == nil { t.Errorf("EnclosureURL(%q) should have failed", bad) } } |
