iTunes and RSS 2.0 Podcast Generator in Golang

Overview

GoDoc Build Status Coverage Status Go Report Card MIT License

podcast

Package podcast generates a fully compliant iTunes and RSS 2.0 podcast feed for GoLang using a simple API.

Full documentation with detailed examples located at https://godoc.org/github.com/eduncan911/podcast

Usage

To use, go get and import the package like your typical GoLang library.

$ go get -u github.com/eduncan911/podcast

import "github.com/eduncan911/podcast"

The API exposes a number of method receivers on structs that implements the logic required to comply with the specifications and ensure a compliant feed. A number of overrides occur to help with iTunes visibility of your episodes.

Notably, the Podcast.AddItem function performs most of the heavy lifting by taking the Item input and performing validation, overrides and duplicate setters through the feed.

Full detailed Examples of the API are at https://godoc.org/github.com/eduncan911/podcast.

Go Modules

This library is supported on GoLang 1.7 and higher.

We have implemented Go Modules support and the CI pipeline shows it working with new installs, tested with Go 1.13. To keep 1.7 compatibility, we use go mod vendor to maintain the vendor/ folder for older 1.7 and later runtimes.

If either runtime has an issue, please create an Issue and I will address.

Extensibility

For version 1.x, you are not restricted in having full control over your feeds. You may choose to skip the API methods and instead use the structs directly. The fields have been grouped by RSS 2.0 and iTunes fields with iTunes specific fields all prefixed with the letter I.

However, do note that the 2.x version currently in progress will break this extensibility and enforce API methods going forward. This is to ensure that the feed can both be marshalled, and unmarshalled back and forth (current 1.x branch can only be unmarshalled - hence the work for 2.x).

Fuzzing Inputs

go-fuzz has been added in 1.4.1, covering all exported API methods. They have been ran extensively and no issues have come out of them yet (most tests were ran overnight, over about 11 hours with zero crashes).

If you wish to help fuzz the inputs, with Go 1.13 or later you can run go-fuzz on any of the inputs.

go get -u github.com/dvyukov/go-fuzz/go-fuzz
go get -u github.com/dvyukov/go-fuzz/go-fuzz-build
go get -u github.com/eduncan911/podcast
cd $GOPATH/src/github.com/eduncan911/podcast
go-fuzz-build
go-fuzz -func FuzzPodcastAddItem

To obtain a list of available funcs to pass, just run go-fuzz without any parameters:

$ go-fuzz
2020/02/13 07:27:32 -func flag not provided, but multiple fuzz functions available:
FuzzItemAddDuration, FuzzItemAddEnclosure, FuzzItemAddImage, FuzzItemAddPubDate,
FuzzItemAddSummary, FuzzPodcastAddAtomLink, FuzzPodcastAddAuthor, FuzzPodcastAddCategory,
FuzzPodcastAddImage, FuzzPodcastAddItem, FuzzPodcastAddLastBuildDate, FuzzPodcastAddPubDate,
FuzzPodcastAddSubTitle, FuzzPodcastAddSummary, FuzzPodcastBytes, FuzzPodcastEncode,
FuzzPodcastNew

If you do find an issue, please raise an issue immediately and I will quickly address.

Roadmap

The 1.x branch is now mostly in maintenance mode, open to PRs. This means no more planned features on the 1.x feature branch is expected. With the success of 6 iTunes-accepted podcasts I have published with this library, and with the feedback from the community, the 1.x releases are now considered stable.

The 2.x branch's primary focus is to allow for bi-direction marshalling both ways. Currently, the 1.x branch only allows unmarshalling to a serial feed. An attempt to marshall a serialized feed back into a Podcast form will error or not work correctly. Note that while the 2.x branch is targeted to remain backwards compatible, it is true if using the public API funcs to set parameters only. Several of the underlying public fields are being removed in order to accommodate the marshalling of serialized data. Therefore, a version 2.x is denoted for this release.

Versioning

We use SemVer versioning schema. You can rest assured that pulling 1.x branches will remain backwards compatible now and into the future.

However, the new 2.x branch, while keeping the same API, is expected break those that bypass the API methods and use the underlying public properties instead.

Release Notes

v1.4.2

* Slim down Go Modules for consumers (#32)

v1.4.1

* Implement fuzz logic testing of exported funcs (#31)
* Upgrade CICD Pipeline Tooling (#31)
* Update documentation for 1.x and 2.3 (#31)
* Allow godoc2ghmd to run without network (#31)

v1.4.0

* Add Go Modules, Update vendor folder (#26, #25)
* Add C.I. GitHub Actions (#25)
* Add additional error checks found by linters (#25)
* Go Fmt enclosure_test.go (#25)

v1.3.2

* Correct count len of UTF8 strings (#9)
* Implement duration parser (#8)
* Fix Github and GoDocs Markdown (#14)
* Move podcast.go Private Methods to Respected Files (#12)
* Allow providing GUID on Podcast (#15)

v1.3.1

* increased itunes compliance after feedback from Apple:
  - specified what categories should be set with AddCategory().
  - enforced title and link as part of Image.
* added Podcast.AddAtomLink() for more broad compliance to readers.

v1.3.0

* fixes Item.Duration being set incorrectly.
* changed Item.AddEnclosure() parameter definition (Bytes not Seconds!).
* added Item.AddDuration formatting and override.
* added more documentation surrounding Item.Enclosure{}

v1.2.1

* added Podcast.AddSubTitle() and truncating to 64 chars.
* added a number of Guards to protect against empty fields.

v1.2.0

* added Podcast.AddPubDate() and Podcast.AddLastBuildDate() overrides.
* added Item.AddImage() to mask some cumbersome addition of IImage.
* added Item.AddPubDate to simply datetime setters.
* added more examples (mostly around Item struct).
* tweaked some documentation.

v1.1.0

* Enabling CDATA in ISummary fields for Podcast and Channel.

v1.0.0

* Initial release.
* Full documentation, full examples and complete code coverage.

References

RSS 2.0: https://cyber.harvard.edu/rss/rss.html

Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233

Example:

Click to expand code.
// ResponseWriter example using Podcast.Encode(w io.Writer).
	//
	httpHandler := func(w http.ResponseWriter, r *http.Request) {
	
	    // instantiate a new Podcast
	    p := podcast.New(
	        "eduncan911 Podcasts",
	        "http://eduncan911.com/",
	        "An example Podcast",
	        &pubDate, &updatedDate,
	    )
	
	    // add some channel properties
	    p.AddAuthor("Jane Doe", "[email protected]")
	    p.AddAtomLink("http://eduncan911.com/feed.rss")
	    p.AddImage("http://janedoe.com/i.jpg")
	    p.AddSummary(`link <a href="http://example.com">example.com</a>`)
	    p.IExplicit = "no"
	
	    for i := int64(1); i < 3; i++ {
	        n := strconv.FormatInt(i, 10)
	        d := pubDate.AddDate(0, 0, int(i))
	
	        // create an Item
	        item := podcast.Item{
	            Title:       "Episode " + n,
	            Link:        "http://example.com/" + n + ".mp3",
	            Description: "Description for Episode " + n,
	            PubDate:     &d,
	        }
	        item.AddImage("http://example.com/episode-" + n + ".png")
	        item.AddSummary(`item <a href="http://example.com">example.com</a>`)
	        // add a Download to the Item
	        item.AddEnclosure("http://e.com/"+n+".mp3", podcast.MP3, 55*(i+1))
	
	        // add the Item and check for validation errors
	        if _, err := p.AddItem(item); err != nil {
	            fmt.Println(item.Title, ": error", err.Error())
	            return
	        }
	    }
	
	    // set the Content Type to that of XML
	    w.Header().Set("Content-Type", "application/xml")
	
	    // finally, Encode and write the Podcast to the ResponseWriter.
	    //
	    // a simple pattern is to handle any errors within this check.
	    // alternatively if using middleware, you can just return
	    // the Podcast entity as it also implements the io.Writer interface
	    // that complies with several middleware packages.
	    if err := p.Encode(w); err != nil {
	        http.Error(w, err.Error(), http.StatusInternalServerError)
	    }
	}
	
	rr := httptest.NewRecorder()
	httpHandler(rr, nil)
	os.Stdout.Write(rr.Body.Bytes())
	// Output:
	// <?xml version="1.0" encoding="UTF-8"?>
	// <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
	//   <channel>
	//     <title>eduncan911 Podcasts</title>
	//     <link>http://eduncan911.com/</link>
	//     <description>An example Podcast</description>
	//     <generator>go podcast v1.3.1 (github.com/eduncan911/podcast)</generator>
	//     <language>en-us</language>
	//     <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate>
	//     <managingEditor>[email protected] (Jane Doe)</managingEditor>
	//     <pubDate>Sat, 04 Feb 2017 08:21:52 +0000</pubDate>
	//     <image>
	//       <url>http://janedoe.com/i.jpg</url>
	//       <title>eduncan911 Podcasts</title>
	//       <link>http://eduncan911.com/</link>
	//     </image>
	//     <atom:link href="http://eduncan911.com/feed.rss" rel="self" type="application/rss+xml"></atom:link>
	//     <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//     <itunes:summary><![CDATA[link <a href="http://example.com">example.com</a>]]></itunes:summary>
	//     <itunes:image href="http://janedoe.com/i.jpg"></itunes:image>
	//     <itunes:explicit>no</itunes:explicit>
	//     <item>
	//       <guid>http://e.com/1.mp3</guid>
	//       <title>Episode 1</title>
	//       <link>http://example.com/1.mp3</link>
	//       <description>Description for Episode 1</description>
	//       <pubDate>Sun, 05 Feb 2017 08:21:52 +0000</pubDate>
	//       <enclosure url="http://e.com/1.mp3" length="110" type="audio/mpeg"></enclosure>
	//       <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//       <itunes:summary><![CDATA[item <a href="http://example.com">example.com</a>]]></itunes:summary>
	//       <itunes:image href="http://example.com/episode-1.png"></itunes:image>
	//     </item>
	//     <item>
	//       <guid>http://e.com/2.mp3</guid>
	//       <title>Episode 2</title>
	//       <link>http://example.com/2.mp3</link>
	//       <description>Description for Episode 2</description>
	//       <pubDate>Mon, 06 Feb 2017 08:21:52 +0000</pubDate>
	//       <enclosure url="http://e.com/2.mp3" length="165" type="audio/mpeg"></enclosure>
	//       <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//       <itunes:summary><![CDATA[item <a href="http://example.com">example.com</a>]]></itunes:summary>
	//       <itunes:image href="http://example.com/episode-2.png"></itunes:image>
	//     </item>
	//   </channel>
	// </rss>

Example:

Click to expand code.
// instantiate a new Podcast
	p := podcast.New(
	    "Sample Podcasts",
	    "http://example.com/",
	    "An example Podcast",
	    &createdDate, &updatedDate,
	)
	
	// add some channel properties
	p.ISubtitle = "A simple Podcast"
	p.AddSummary(`link <a href="http://example.com">example.com</a>`)
	p.AddImage("http://example.com/podcast.jpg")
	p.AddAuthor("Jane Doe", "[email protected]")
	p.AddAtomLink("http://example.com/atom.rss")
	
	for i := int64(9); i < 11; i++ {
	    n := strconv.FormatInt(i, 10)
	    d := pubDate.AddDate(0, 0, int(i))
	
	    // create an Item
	    item := podcast.Item{
	        Title:       "Episode " + n,
	        Description: "Description for Episode " + n,
	        ISubtitle:   "A simple episode " + n,
	        PubDate:     &d,
	    }
	    item.AddImage("http://example.com/episode-" + n + ".png")
	    item.AddSummary(`item k <a href="http://example.com">example.com</a>`)
	    // add a Download to the Item
	    item.AddEnclosure("http://example.com/"+n+".mp3", podcast.MP3, 55*(i+1))
	
	    // add the Item and check for validation errors
	    if _, err := p.AddItem(item); err != nil {
	        os.Stderr.WriteString("item validation error: " + err.Error())
	    }
	}
	
	// Podcast.Encode writes to an io.Writer
	if err := p.Encode(os.Stdout); err != nil {
	    fmt.Println("error writing to stdout:", err.Error())
	}
	
	// Output:
	// <?xml version="1.0" encoding="UTF-8"?>
	// <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
	//   <channel>
	//     <title>Sample Podcasts</title>
	//     <link>http://example.com/</link>
	//     <description>An example Podcast</description>
	//     <generator>go podcast v1.3.1 (github.com/eduncan911/podcast)</generator>
	//     <language>en-us</language>
	//     <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate>
	//     <managingEditor>[email protected] (Jane Doe)</managingEditor>
	//     <pubDate>Wed, 01 Feb 2017 08:21:52 +0000</pubDate>
	//     <image>
	//       <url>http://example.com/podcast.jpg</url>
	//       <title>Sample Podcasts</title>
	//       <link>http://example.com/</link>
	//     </image>
	//     <atom:link href="http://example.com/atom.rss" rel="self" type="application/rss+xml"></atom:link>
	//     <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//     <itunes:subtitle>A simple Podcast</itunes:subtitle>
	//     <itunes:summary><![CDATA[link <a href="http://example.com">example.com</a>]]></itunes:summary>
	//     <itunes:image href="http://example.com/podcast.jpg"></itunes:image>
	//     <item>
	//       <guid>http://example.com/9.mp3</guid>
	//       <title>Episode 9</title>
	//       <link>http://example.com/9.mp3</link>
	//       <description>Description for Episode 9</description>
	//       <pubDate>Mon, 13 Feb 2017 08:21:52 +0000</pubDate>
	//       <enclosure url="http://example.com/9.mp3" length="550" type="audio/mpeg"></enclosure>
	//       <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//       <itunes:subtitle>A simple episode 9</itunes:subtitle>
	//       <itunes:summary><![CDATA[item k <a href="http://example.com">example.com</a>]]></itunes:summary>
	//       <itunes:image href="http://example.com/episode-9.png"></itunes:image>
	//     </item>
	//     <item>
	//       <guid>http://example.com/10.mp3</guid>
	//       <title>Episode 10</title>
	//       <link>http://example.com/10.mp3</link>
	//       <description>Description for Episode 10</description>
	//       <pubDate>Tue, 14 Feb 2017 08:21:52 +0000</pubDate>
	//       <enclosure url="http://example.com/10.mp3" length="605" type="audio/mpeg"></enclosure>
	//       <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//       <itunes:subtitle>A simple episode 10</itunes:subtitle>
	//       <itunes:summary><![CDATA[item k <a href="http://example.com">example.com</a>]]></itunes:summary>
	//       <itunes:image href="http://example.com/episode-10.png"></itunes:image>
	//     </item>
	//   </channel>
	// </rss>

Table of Contents

Imported Packages

Index

Examples

Package files

atomlink.go author.go doc.go enclosure.go image.go item.go itunes.go podcast.go textinput.go

type AtomLink

type AtomLink struct {
    XMLName xml.Name `xml:"atom:link"`
    HREF    string   `xml:"href,attr"`
    Rel     string   `xml:"rel,attr"`
    Type    string   `xml:"type,attr"`
}

AtomLink represents the Atom reference link.

type Author

type Author struct {
    XMLName xml.Name `xml:"itunes:owner"`
    Name    string   `xml:"itunes:name"`
    Email   string   `xml:"itunes:email"`
}

Author represents a named author and email.

For iTunes compliance, both Name and Email are required.

type Enclosure

type Enclosure struct {
    XMLName xml.Name `xml:"enclosure"`

    // URL is the downloadable url for the content. (Required)
    URL string `xml:"url,attr"`

    // Length is the size in Bytes of the download. (Required)
    Length int64 `xml:"-"`
    // LengthFormatted is the size in Bytes of the download. (Required)
    //
    // This field gets overwritten with the API when setting Length.
    LengthFormatted string `xml:"length,attr"`

    // Type is MIME type encoding of the download. (Required)
    Type EnclosureType `xml:"-"`
    // TypeFormatted is MIME type encoding of the download. (Required)
    //
    // This field gets overwritten with the API when setting Type.
    TypeFormatted string `xml:"type,attr"`
}

Enclosure represents a download enclosure.

type EnclosureType

type EnclosureType int

EnclosureType specifies the type of the enclosure.

const (
    M4A EnclosureType = iota
    M4V
    MP4
    MP3
    MOV
    PDF
    EPUB
)

EnclosureType specifies the type of the enclosure.

func (EnclosureType) String

func (et EnclosureType) String() string

String returns the MIME type encoding of the specified EnclosureType.

type ICategory

type ICategory struct {
    XMLName     xml.Name `xml:"itunes:category"`
    Text        string   `xml:"text,attr"`
    ICategories []*ICategory
}

ICategory is a 2-tier classification system for iTunes.

type IImage

type IImage struct {
    XMLName xml.Name `xml:"itunes:image"`
    HREF    string   `xml:"href,attr"`
}

IImage represents an iTunes image.

Podcast feeds contain artwork that is a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, 72 dpi, in JPEG or PNG format with appropriate file extensions (.jpg, .png), and in the RGB colorspace. To optimize images for mobile devices, Apple recommends compressing your image files.

type ISummary

type ISummary struct {
    XMLName xml.Name `xml:"itunes:summary"`
    Text    string   `xml:",cdata"`
}

ISummary is a 4000 character rich-text field for the itunes:summary tag.

This is rendered as CDATA which allows for HTML tags such as <a href="">.

type Image

type Image struct {
    XMLName     xml.Name `xml:"image"`
    URL         string   `xml:"url"`
    Title       string   `xml:"title"`
    Link        string   `xml:"link"`
    Description string   `xml:"description,omitempty"`
    Width       int      `xml:"width,omitempty"`
    Height      int      `xml:"height,omitempty"`
}

Image represents an image.

Podcast feeds contain artwork that is a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, 72 dpi, in JPEG or PNG format with appropriate file extensions (.jpg, .png), and in the RGB colorspace. To optimize images for mobile devices, Apple recommends compressing your image files.

type Item

type Item struct {
    XMLName          xml.Name   `xml:"item"`
    GUID             string     `xml:"guid"`
    Title            string     `xml:"title"`
    Link             string     `xml:"link"`
    Description      string     `xml:"description"`
    Author           *Author    `xml:"-"`
    AuthorFormatted  string     `xml:"author,omitempty"`
    Category         string     `xml:"category,omitempty"`
    Comments         string     `xml:"comments,omitempty"`
    Source           string     `xml:"source,omitempty"`
    PubDate          *time.Time `xml:"-"`
    PubDateFormatted string     `xml:"pubDate,omitempty"`
    Enclosure        *Enclosure

    // https://help.apple.com/itc/podcasts_connect/#/itcb54353390
    IAuthor            string `xml:"itunes:author,omitempty"`
    ISubtitle          string `xml:"itunes:subtitle,omitempty"`
    ISummary           *ISummary
    IImage             *IImage
    IDuration          string `xml:"itunes:duration,omitempty"`
    IExplicit          string `xml:"itunes:explicit,omitempty"`
    IIsClosedCaptioned string `xml:"itunes:isClosedCaptioned,omitempty"`
    IOrder             string `xml:"itunes:order,omitempty"`
}

Item represents a single entry in a podcast.

Article minimal requirements are:

  • Title
  • Description
  • Link

Audio minimal requirements are:

  • Title
  • Description
  • Enclosure (HREF, Type and Length all required)

Recommendations:

  • Setting the minimal fields sets most of other fields, including iTunes.
  • Use the Published time.Time setting instead of PubDate.
  • Always set an Enclosure.Length, to be nice to your downloaders.
  • Use Enclosure.Type instead of setting TypeFormatted for valid extensions.

func (*Item) AddDuration

func (i *Item) AddDuration(durationInSeconds int64)

AddDuration adds the duration to the iTunes duration field.

Example:

Click to expand code.
i := podcast.Item{
	    Title:       "item title",
	    Description: "item desc",
	    Link:        "item link",
	}
	d := int64(533)
	
	// add the Duration in Seconds
	i.AddDuration(d)
	
	fmt.Println(i.IDuration)
	// Output:
	// 8:53

func (*Item) AddEnclosure

func (i *Item) AddEnclosure(
    url string, enclosureType EnclosureType, lengthInBytes int64)

AddEnclosure adds the downloadable asset to the podcast Item.

func (*Item) AddImage

func (i *Item) AddImage(url string)

AddImage adds the image as an iTunes-only IImage. RSS 2.0 does not have the specification of Images at the Item level.

Podcast feeds contain artwork that is a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, 72 dpi, in JPEG or PNG format with appropriate file extensions (.jpg, .png), and in the RGB colorspace. To optimize images for mobile devices, Apple recommends compressing your image files.

func (*Item) AddPubDate

func (i *Item) AddPubDate(datetime *time.Time)

AddPubDate adds the datetime as a parsed PubDate.

UTC time is used by default.

Example:

Click to expand code.
p := podcast.New("title", "link", "description", nil, nil)
	i := podcast.Item{
	    Title:       "item title",
	    Description: "item desc",
	    Link:        "item link",
	}
	d := pubDate.AddDate(0, 0, -11)
	
	// add the pub date
	i.AddPubDate(&d)
	
	// before adding
	if i.PubDate != nil {
	    fmt.Println(i.PubDateFormatted, *i.PubDate)
	}
	
	// this should not override with Podcast.PubDate
	if _, err := p.AddItem(i); err != nil {
	    fmt.Println(err)
	}
	
	// after adding item
	fmt.Println(i.PubDateFormatted, *i.PubDate)
	// Output:
	// Tue, 24 Jan 2017 08:21:52 +0000 2017-01-24 08:21:52 +0000 UTC
	// Tue, 24 Jan 2017 08:21:52 +0000 2017-01-24 08:21:52 +0000 UTC

func (*Item) AddSummary

func (i *Item) AddSummary(summary string)

AddSummary adds the iTunes summary.

Limit: 4000 characters

Note that this field is a CDATA encoded field which allows for rich text such as html links: <a href="<a href="http://www.apple.com">http://www.apple.com</a>">Apple</a>.

type Podcast

type Podcast struct {
    XMLName        xml.Name `xml:"channel"`
    Title          string   `xml:"title"`
    Link           string   `xml:"link"`
    Description    string   `xml:"description"`
    Category       string   `xml:"category,omitempty"`
    Cloud          string   `xml:"cloud,omitempty"`
    Copyright      string   `xml:"copyright,omitempty"`
    Docs           string   `xml:"docs,omitempty"`
    Generator      string   `xml:"generator,omitempty"`
    Language       string   `xml:"language,omitempty"`
    LastBuildDate  string   `xml:"lastBuildDate,omitempty"`
    ManagingEditor string   `xml:"managingEditor,omitempty"`
    PubDate        string   `xml:"pubDate,omitempty"`
    Rating         string   `xml:"rating,omitempty"`
    SkipHours      string   `xml:"skipHours,omitempty"`
    SkipDays       string   `xml:"skipDays,omitempty"`
    TTL            int      `xml:"ttl,omitempty"`
    WebMaster      string   `xml:"webMaster,omitempty"`
    Image          *Image
    TextInput      *TextInput
    AtomLink       *AtomLink

    // https://help.apple.com/itc/podcasts_connect/#/itcb54353390
    IAuthor     string `xml:"itunes:author,omitempty"`
    ISubtitle   string `xml:"itunes:subtitle,omitempty"`
    ISummary    *ISummary
    IBlock      string `xml:"itunes:block,omitempty"`
    IImage      *IImage
    IDuration   string  `xml:"itunes:duration,omitempty"`
    IExplicit   string  `xml:"itunes:explicit,omitempty"`
    IComplete   string  `xml:"itunes:complete,omitempty"`
    INewFeedURL string  `xml:"itunes:new-feed-url,omitempty"`
    IOwner      *Author // Author is formatted for itunes as-is
    ICategories []*ICategory

    Items []*Item
    // contains filtered or unexported fields
}

Podcast represents a podcast.

func New

func New(title, link, description string,
    pubDate, lastBuildDate *time.Time) Podcast

New instantiates a Podcast with required parameters.

Nil-able fields are optional but recommended as they are formatted to the expected proper formats.

Example:

Click to expand code.
ti, l, d := "title", "link", "description"
	
	// instantiate a new Podcast
	p := podcast.New(ti, l, d, &pubDate, &updatedDate)
	
	fmt.Println(p.Title, p.Link, p.Description, p.Language)
	fmt.Println(p.PubDate, p.LastBuildDate)
	// Output:
	// title link description en-us
	// Sat, 04 Feb 2017 08:21:52 +0000 Mon, 06 Feb 2017 08:21:52 +0000

func (*Podcast) AddAtomLink

func (p *Podcast) AddAtomLink(href string)

AddAtomLink adds a FQDN reference to an atom feed.

func (*Podcast) AddAuthor

func (p *Podcast) AddAuthor(name, email string)

AddAuthor adds the specified Author to the podcast.

Example:

Click to expand code.
p := podcast.New("title", "link", "description", nil, nil)
	
	// add the Author
	p.AddAuthor("the name", "[email protected]")
	
	fmt.Println(p.ManagingEditor)
	fmt.Println(p.IAuthor)
	// Output:
	// [email protected] (the name)
	// [email protected] (the name)

func (*Podcast) AddCategory

func (p *Podcast) AddCategory(category string, subCategories []string)

AddCategory adds the category to the Podcast.

ICategory can be listed multiple times.

Calling this method multiple times will APPEND the category to the existing list, if any, including ICategory.

Note that Apple iTunes has a specific list of categories that only can be used and will invalidate the feed if deviated from the list. That list is as follows.

* Arts
  * Design
  * Fashion & Beauty
  * Food
  * Literature
  * Performing Arts
  * Visual Arts
* Business
  * Business News
  * Careers
  * Investing
  * Management & Marketing
  * Shopping
* Comedy
* Education
  * Education Technology
  * Higher Education
  * K-12
  * Language Courses
  * Training
* Games & Hobbies
  * Automotive
  * Aviation
  * Hobbies
  * Other Games
  * Video Games
* Government & Organizations
  * Local
  * National
  * Non-Profit
  * Regional
* Health
  * Alternative Health
  * Fitness & Nutrition
  * Self-Help
  * Sexuality
* Kids & Family
* Music
* News & Politics
* Religion & Spirituality
  * Buddhism
  * Christianity
  * Hinduism
  * Islam
  * Judaism
  * Other
  * Spirituality
* Science & Medicine
  * Medicine
  * Natural Sciences
  * Social Sciences
* Society & Culture
  * History
  * Personal Journals
  * Philosophy
  * Places & Travel
* Sports & Recreation
  * Amateur
  * College & High School
  * Outdoor
  * Professional
* Technology
  * Gadgets
  * Podcasting
  * Software How-To
  * Tech News
* TV & Film

Example:

Click to expand code.
p := podcast.New("title", "link", "description", nil, nil)
	
	// add the Category
	p.AddCategory("Bombay", nil)
	p.AddCategory("American", []string{"Longhair", "Shorthair"})
	p.AddCategory("Siamese", nil)
	
	fmt.Println(len(p.ICategories), len(p.ICategories[1].ICategories))
	fmt.Println(p.Category)
	// Output:
	// 3 2
	// Bombay,American,Siamese

func (*Podcast) AddImage

func (p *Podcast) AddImage(url string)

AddImage adds the specified Image to the Podcast.

Podcast feeds contain artwork that is a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, 72 dpi, in JPEG or PNG format with appropriate file extensions (.jpg, .png), and in the RGB colorspace. To optimize images for mobile devices, Apple recommends compressing your image files.

Example:

Click to expand code.
p := podcast.New("title", "link", "description", nil, nil)
	
	// add the Image
	p.AddImage("http://example.com/image.jpg")
	
	if p.Image != nil && p.IImage != nil {
	    fmt.Println(p.Image.URL)
	    fmt.Println(p.IImage.HREF)
	}
	// Output:
	// http://example.com/image.jpg
	// http://example.com/image.jpg

func (*Podcast) AddItem

func (p *Podcast) AddItem(i Item) (int, error)

AddItem adds the podcast episode. It returns a count of Items added or any errors in validation that may have occurred.

This method takes the "itunes overrides" approach to populating itunes tags according to the overrides rules in the specification. This not only complies completely with iTunes parsing rules; but, it also displays what is possible to be set on an individual episode level – if you wish to have more fine grain control over your content.

This method imposes strict validation of the Item being added to confirm to Podcast and iTunes specifications.

Article minimal requirements are:

* Title
* Description
* Link

Audio, Video and Downloads minimal requirements are:

* Title
* Description
* Enclosure (HREF, Type and Length all required)

The following fields are always overwritten (don't set them):

* GUID
* PubDateFormatted
* AuthorFormatted
* Enclosure.TypeFormatted
* Enclosure.LengthFormatted

Recommendations:

* Just set the minimal fields: the rest get set for you.
* Always set an Enclosure.Length, to be nice to your downloaders.
* Follow Apple's best practices to enrich your podcasts:
  <a href="https://help.apple.com/itc/podcasts_connect/#/itc2b3780e76">https://help.apple.com/itc/podcasts_connect/#/itc2b3780e76</a>
* For specifications of itunes tags, see:
  <a href="https://help.apple.com/itc/podcasts_connect/#/itcb54353390">https://help.apple.com/itc/podcasts_connect/#/itcb54353390</a>

Example:

Click to expand code.
p := podcast.New("title", "link", "description", &pubDate, &updatedDate)
	p.AddAuthor("the name", "[email protected]")
	p.AddImage("http://example.com/image.jpg")
	
	// create an Item
	date := pubDate.AddDate(0, 0, 77)
	item := podcast.Item{
	    Title:       "Episode 1",
	    Description: "Description for Episode 1",
	    ISubtitle:   "A simple episode 1",
	    PubDate:     &date,
	}
	item.AddEnclosure(
	    "http://example.com/1.mp3",
	    podcast.MP3,
	    183,
	)
	item.AddSummary("See more at <a href=\"http://example.com\">Here</a>")
	
	// add the Item
	if _, err := p.AddItem(item); err != nil {
	    fmt.Println("item validation error: " + err.Error())
	}
	
	if len(p.Items) != 1 {
	    fmt.Println("expected 1 item in the collection")
	}
	pp := p.Items[0]
	fmt.Println(
	    pp.GUID, pp.Title, pp.Link, pp.Description, pp.Author,
	    pp.AuthorFormatted, pp.Category, pp.Comments, pp.Source,
	    pp.PubDate, pp.PubDateFormatted, *pp.Enclosure,
	    pp.IAuthor, pp.IDuration, pp.IExplicit, pp.IIsClosedCaptioned,
	    pp.IOrder, pp.ISubtitle, pp.ISummary)
	// Output:
	// http://example.com/1.mp3 Episode 1 http://example.com/1.mp3 Description for Episode 1 &{{ }  [email protected] (the name)}     2017-04-22 08:21:52 +0000 UTC Sat, 22 Apr 2017 08:21:52 +0000 {{ } http://example.com/1.mp3 183 183 audio/mpeg audio/mpeg} [email protected] (the name)     A simple episode 1 &{{ } See more at <a href="http://example.com">Here</a>}

func (*Podcast) AddLastBuildDate

func (p *Podcast) AddLastBuildDate(datetime *time.Time)

AddLastBuildDate adds the datetime as a parsed PubDate.

UTC time is used by default.

Example:

Click to expand code.
p := podcast.New("title", "link", "description", nil, nil)
	d := pubDate.AddDate(0, 0, -7)
	
	p.AddLastBuildDate(&d)
	
	fmt.Println(p.LastBuildDate)
	// Output:
	// Sat, 28 Jan 2017 08:21:52 +0000

func (*Podcast) AddPubDate

func (p *Podcast) AddPubDate(datetime *time.Time)

AddPubDate adds the datetime as a parsed PubDate.

UTC time is used by default.

Example:

Click to expand code.
p := podcast.New("title", "link", "description", nil, nil)
	d := pubDate.AddDate(0, 0, -5)
	
	p.AddPubDate(&d)
	
	fmt.Println(p.PubDate)
	// Output:
	// Mon, 30 Jan 2017 08:21:52 +0000

func (*Podcast) AddSubTitle

func (p *Podcast) AddSubTitle(subTitle string)

AddSubTitle adds the iTunes subtitle that is displayed with the title in iTunes.

Note that this field should be just a few words long according to Apple. This method will truncate the string to 64 chars if too long with "..."

func (*Podcast) AddSummary

func (p *Podcast) AddSummary(summary string)

AddSummary adds the iTunes summary.

Limit: 4000 characters

Note that this field is a CDATA encoded field which allows for rich text such as html links: <a href="<a href="http://www.apple.com">http://www.apple.com</a>">Apple</a>.

Example:

Click to expand code.
p := podcast.New("title", "link", "description", nil, nil)
	
	// add a summary
	p.AddSummary(`A very cool podcast with a long summary!
	
	See more at our website: <a href="http://example.com">example.com</a>
	`)
	
	if p.ISummary != nil {
	    fmt.Println(p.ISummary.Text)
	}
	// Output:
	// A very cool podcast with a long summary!
	//
	// See more at our website: <a href="http://example.com">example.com</a>

func (*Podcast) Bytes

func (p *Podcast) Bytes() []byte

Bytes returns an encoded []byte slice.

Example:

Click to expand code.
p := podcast.New(
	    "eduncan911 Podcasts",
	    "http://eduncan911.com/",
	    "An example Podcast",
	    &pubDate, &updatedDate,
	)
	p.AddAuthor("Jane Doe", "[email protected]")
	p.AddImage("http://janedoe.com/i.jpg")
	p.AddSummary(`A very cool podcast with a long summary using Bytes()!
	
	See more at our website: <a href="http://example.com">example.com</a>
	`)
	
	for i := int64(5); i < 7; i++ {
	    n := strconv.FormatInt(i, 10)
	    d := pubDate.AddDate(0, 0, int(i+3))
	
	    item := podcast.Item{
	        Title:       "Episode " + n,
	        Link:        "http://example.com/" + n + ".mp3",
	        Description: "Description for Episode " + n,
	        PubDate:     &d,
	    }
	    if _, err := p.AddItem(item); err != nil {
	        fmt.Println(item.Title, ": error", err.Error())
	        break
	    }
	}
	
	// call Podcast.Bytes() to return a byte array
	os.Stdout.Write(p.Bytes())
	
	// Output:
	// <?xml version="1.0" encoding="UTF-8"?>
	// <rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
	//   <channel>
	//     <title>eduncan911 Podcasts</title>
	//     <link>http://eduncan911.com/</link>
	//     <description>An example Podcast</description>
	//     <generator>go podcast v1.3.1 (github.com/eduncan911/podcast)</generator>
	//     <language>en-us</language>
	//     <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate>
	//     <managingEditor>[email protected] (Jane Doe)</managingEditor>
	//     <pubDate>Sat, 04 Feb 2017 08:21:52 +0000</pubDate>
	//     <image>
	//       <url>http://janedoe.com/i.jpg</url>
	//       <title>eduncan911 Podcasts</title>
	//       <link>http://eduncan911.com/</link>
	//     </image>
	//     <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//     <itunes:summary><![CDATA[A very cool podcast with a long summary using Bytes()!
	//
	// See more at our website: <a href="http://example.com">example.com</a>
	// ]]></itunes:summary>
	//     <itunes:image href="http://janedoe.com/i.jpg"></itunes:image>
	//     <item>
	//       <guid>http://example.com/5.mp3</guid>
	//       <title>Episode 5</title>
	//       <link>http://example.com/5.mp3</link>
	//       <description>Description for Episode 5</description>
	//       <pubDate>Sun, 12 Feb 2017 08:21:52 +0000</pubDate>
	//       <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//       <itunes:image href="http://janedoe.com/i.jpg"></itunes:image>
	//     </item>
	//     <item>
	//       <guid>http://example.com/6.mp3</guid>
	//       <title>Episode 6</title>
	//       <link>http://example.com/6.mp3</link>
	//       <description>Description for Episode 6</description>
	//       <pubDate>Mon, 13 Feb 2017 08:21:52 +0000</pubDate>
	//       <itunes:author>[email protected] (Jane Doe)</itunes:author>
	//       <itunes:image href="http://janedoe.com/i.jpg"></itunes:image>
	//     </item>
	//   </channel>
	// </rss>

func (*Podcast) Encode

func (p *Podcast) Encode(w io.Writer) error

Encode writes the bytes to the io.Writer stream in RSS 2.0 specification.

func (*Podcast) String

func (p *Podcast) String() string

String encodes the Podcast state to a string.

type TextInput

type TextInput struct {
    XMLName     xml.Name `xml:"textInput"`
    Title       string   `xml:"title"`
    Description string   `xml:"description"`
    Name        string   `xml:"name"`
    Link        string   `xml:"link"`
}

TextInput represents text inputs.


Generated by godoc2ghmd

Comments
  • Create release for v1.3.1

    Create release for v1.3.1

    With go modules being announced https://blog.golang.org/modules2019 go get will now download v1.3.0 because it is the most recent tag.

    Can the lastest master be tagged with v1.3.1?

    opened by Reidsy 7
  • Mixup between enclosure.length and itunes:duration

    Mixup between enclosure.length and itunes:duration

    Hi @eduncan911,

    first of all, thanks for the work you've put into building this library.

    I've been working with it and I think there is a slight mixup between enclosure.length attribute and itunes:duration tag. According to RSS 2.0 spec and iTunes podcast spec, the length attribute of enclosure should be the size of the file in bytes. However, the AddEnclosure method signature takes an argument called lengthInSeconds, which I guess is misleading. Moreover, the Podcast.AddItem method set's the newly added Item's IDuration (which should be duration in as in length of time) by using the Item's Enclosure.Length.

    My suggestions/questions:

    • Enclosure.Length should be used to represent the size of the file in bytes.
    • Item.AddEnclosure() method signature should be changed so that lengthInSeconds would become lengthInBytes.
    • I guess the Item type is missing an additional field which would store the length in seconds (or milisenconds?) and which could be used to format the IDuration field?

    What is your opinion?

    bug 
    opened by naglis 6
  • [Question] Make the podcast.New() smarter?

    [Question] Make the podcast.New() smarter?

    I didn't expect people to find this repo any time soon and I see a number of people already using it. Oops. Hehe.

    There is a breaking change I wanted to do; but, I am holding off because of others that may be using it.

    I want to change the podcast.New() method because of a number of reasons:

    • Make it simpler (don't ask for datetimes, use helper methods for them)
    • add Validation
    • with Validation, comes error handling

    Basically, I want to change this:

    func New(title, link, description string, pubDate, lastBuildDate *time.Time) Podcast
    

    To this:

    func New(title, link, description string) (Podcast, error)
    

    And add these:

    ~~func (p *Podcast) AddPublishedDate(t time.Time)~~ ~~func (p *Podcast) AddLastBuildDate(t time.Time)~~

    Notice that Podcast.New() no longer will take in time.Time, and now also returns an error if validation isn't correct (e.g. missing one of title, link or description).

    Breaking Change This would obviously be a breaking change to anyone currently using it (and not vendoring). Therefore, it would require a version bump to 2.x.x.

    IMO
    I'm on the fence about this. I like APIs that allow package.New() without requiring me to check for errors. They should guarantee a result with package.New() that just works. That was my originally intention when I created the New() method.

    But at the same time, how do you know your Podcast is valid if we don't have validation checks? The answer to that is simple: Apple rejects your podcast when you submit it for not being valid.


    Update 17/02/16: Already added the two new funcs. Just need to discuss the New() question now.

    question 
    opened by eduncan911 4
  • Create Release 1.3.2

    Create Release 1.3.2

    • Correct count len of UTF8 strings (#9)
    • Implement duration parser (#8)
    • Fix Github and GoDocs Markdown (#14)
    • Move podcast.go Private Methods to Respected Files (#12)
    • Allow providing GUID on Podcast (#15)

    Co-authored-by: Maksym Pavlenk Co-authored-by: Konstantin Chukhlomin Co-authored-by: iwittkau Co-authored-by: Damian Szeluga

    opened by eduncan911 2
  • Add missing Itunes Tags

    Add missing Itunes Tags

    Adds some missing situational itunes tags https://github.com/eduncan911/podcast/issues/13

    Example: //... p := podcast.New( "eduncan911 Podcasts", "http://eduncan911.com/", "An example Podcast", &pubDate, &updatedDate, )

    p.IType = "serial" //... item := podcast.Item{ Title: "Episode " + n, Link: "http://example.com/" + n + ".mp3", Description: "Description for Episode " + n, PubDate: &d, }

    item.IEpisodeType = "bonus" // episode bonus item.ISeason = "1" // 1st season item.ITitle = "Conside Title" // special title, don't specify season or episode number item.IEpisode = "5" // 5th episode //...

    opened by jaderebrasil 2
  • Fix Github and GoDocs Markdown

    Fix Github and GoDocs Markdown

    This PR fixes some issues in documentation that lead to a bad Markdown rendering in a README.md:

    • wrapped few HTML links into inline code blocks
    • fixed categories/subcategories list

    REAMDE.md itself wasn't updated: relying ongodoc2ghmd, but let me know if I need to update it.

    opened by chuhlomin 2
  • Correctly count len of UTF8 strings (fix panics)

    Correctly count len of UTF8 strings (fix panics)

    In current implementation AddSubTitle panics for strings like U17.com - 有妖氣官方頻道丨《鎮魂街》熱血來襲!每週四精彩更新!. This is because len returns the length of string in bytes instead of length in runes (runes = 33, len in bytes > 64), which cases invalid slice range and panics.

    @eduncan911 can you review pls?

    opened by mxpv 2
  • Add go-fuzz to all Func Inputs

    Add go-fuzz to all Func Inputs

    Would be nice to add a new fuzz.go. I took a quick stab at this, and feel kind of restricted to the single Fuzz() method the go-fuzz only allows. I'll keep playing with it.

    I did find https://github.com/dvyukov/go-fuzz/issues/230 that led me to fzgo, which seems to speed up a multiple-package implementation of go-fuzz (creating multiple fuzz.go, within different directories). However, we'd be limited to only public Exported funcs. That still may be ok, given the isolation and coverage we'd get from multiple tests. That directory structure could look like:

    podcast/fuzz.sh # script to kick of all go-fuzz tests at once, using fzgo perhaps
    podcast/fuzz/podcast/fuzz.go # Fuzz() would test all funcs in podcast.go
    podcast/fuzz/item/fuzz.go # Fuzz() would test all funcs in item.go
    

    Or perhaps a structure breaking out each individual func:

    podcast/fuzz.sh # script to kick of all go-fuzz tests at once, using fzgo perhaps
    podcast/fuzz/AddItem/fuzz.go
    podcast/fuzz/AddDuration/fuzz.go
    podcast/fuzz/AddImage/fuzz.go
    ... and so on
    

    I'll continue to experiment with the single fuzz.go approach in the root for now. It's getting quite messy though.

    enhancement help wanted 
    opened by eduncan911 1
  • Tweak: move private methods from podcast.go to respected usage files

    Tweak: move private methods from podcast.go to respected usage files

    When I originally write podcast.go, everything was in that one file.

    As I started to add more and more functionality, it started to make sense to break out that extra functionality to dedicated files. E.g. item.go.

    However, I forgot to move the required private methods used in such files from podcast.go out to their usage.

    Example:

    var parseDuration = func(duration int64) string needs to be moved to where it is being used, item.go.

    Off Topic: You may be wondering why I use var parseDuration = func(duration int64) string to define my functions, instead of func parseDuration(duration int64) string. The reason is because of testability. By assigning it to a variable, it allows me to assign custom functionality during tests - e.g. to purposely break or purposely cause an invalid result - to see how the caller would react.

    opened by eduncan911 1
  • fixes and extends #3 - Item.Duration rework

    fixes and extends #3 - Item.Duration rework

    fixes #3 - Item.Duration being set incorrectly.

    • removing override
    • adding clear documentation
    • changing api parameter name

    adding Item.AddDuration

    • will eventually format seconds for HH:MM:SS format per iTunes specs

    updated test coverage bumped version to 1.3

    bug enhancement 
    opened by eduncan911 1
  • [Feature] create an Item.AddImage() func

    [Feature] create an Item.AddImage() func

    There already is an Podcast.AddImage(), which takes care of the channel's image and iTunes image properties at the root channel.

    The Apple iTunes specification allows for individual episode items to have an <itunes:image> property, and this framework already supports that.

    But, it is a little hokey in the way it is used:

    item.IImage = &podcast.IImage{HREF: "http://example.com/episode-44.jpg"}
    

    This enhancement is to create a helper method that matches the style on the root channel, making it easier to add an image.

    The proposal is to add:

    item.AddImage("http://example.com/episode-44.jpg")
    

    This is to make it more intuitive to the other helper methods.

    I'll get around to it some day. Just a heads up that it's coming.

    enhancement 
    opened by eduncan911 1
  • Refactor iTunes tags for April 2019 changes

    Refactor iTunes tags for April 2019 changes

    There is a number of breaking changes in this commit that is required for the iTunes tag updates.

    • Resolves #35 with AddAuthor() that will now instead set IOwner.Email and .Name, instead of Author - leaving Author to be the new name (without email) per April 2019 suggestions.

    This is an initial draft. More plumbing is required, as well as reworking all Examples and tests.

    One bit of good news: lots of new documentation is now available on iTunes. So I've added a lot of it here.

    opened by eduncan911 0
  • Set podcast.IOwner when calling podcast.AddAuthor(name, email)

    Set podcast.IOwner when calling podcast.AddAuthor(name, email)

    A recent comment on #7 lead me to research the current iteration of itunes' podcast definition.

    I found down deep there are now addition sub-fields embedded within the Owner tag.

    This issue is for me to research if there is indeed a new set of required sub-fields overall.

    A general "refresh" view of all fields to see what has changed in the last four years.

    enhancement 
    opened by eduncan911 2
  • Add Google Play podcast tags

    Add Google Play podcast tags

    This ticket is to enhance the library to generate GooglePlay Podcast tags to the feeds.

    https://support.google.com/googleplay/podcasts/answer/6260341#tags

    Required additional tag, at a Minimal, at the root:

    The following is required by GooglePlay, for authorization of the podcast submission.

    This means we will have to make these requirements in this library, possibly from the podcast.New(...) signature since it does not require Author at this time.

    That would break backwards compatibility. So there is some discussion here on this.

    Another option is to instead strongly suggest that if you want to submit to GooglePlay Podcasts, you must set podcast.AddAuthor(...) or else the feed will not be valid. This would probably be fine if we make it very clear.

    Additional Tags

    The library as it exists handles the rest of the required tags for GooglePlay at both the Podcast and Item levels.

    But, we should enhance the feed and specify the GooglePlay items anyways to make it explicit. The link above has the GooglePlay tags that match the existing iTunes tags that we would follow.

    Categories

    We need to match up the GooglePlay categories with the iTunes categories.

    Option 1: if they all match, no sweat! Use as is. crossing fingers

    Option 2: (much more likely scenario) Refactor the AddCategory() to take in an Enum, and then we write out the appropiate tags for each iTunes and GooglePlay.

    That would break backwards compatibility.

    Option 3: a clumsy approach would be to just add in a bunch of AddGooglePlayX() funcs where X would be the tag, like Category. I'd prefer not to go down that route though.

    Most likely will do Option 2, but breaks the existing. So, Version 2.0.0?

    Tag Priority

    Note that compatibility that GooglePlay does with iTunes tags (iTunes has no such issue).

    enhancement help wanted 
    opened by eduncan911 3
  • Enable Marshaling support

    Enable Marshaling support

    Note, this PR is incomplete. Still needs:

    • additional Marshaling work (remaining: Item)
    • basic testing! haven't even tried this stuff yet
    • code coverage
    • examples

    I am creating this PR early in transparency for others to see and comment on while work continues.

    Backwards Compatibility

    While this is targeting a 2.x point release, I am trying to keep it backwards compatible with the existing 1.x releases.

    So far it remains backwards compatible as long as you used the API methods in 1.x (podcast.New(...), p.AddItem(...), i.AddEnclosure(...), etc etc). These do not change at all from outside perspective, validated by all unit and integration tests (100% code coverage baby!).

    What does break is the removal of specialized fields that were suffixed with "Formatted." For example, Enclosure.TypeFormatted, Enclosure.LengthFormatted, Item.AuthorFormatted, Item.PudDateFormatted and so on. They are all now removed. These were originally added because, frankly, I was too lazy to implement the Marshal interfaces when I originally wrote it - it was a simple hack to just use a 2nd field and manipulate the result during the API calls. These were only used if you bypassed the API methods and attempted to manipulate the Podcast feeds yourself.

    So basically, if you stuck to the API methods you were fine. But, if you used the structs only and did everything manually, you could see some code breakage.

    Marshaling

    I actually came up with a use case lately that I needed to compare previous feeds with newly generated ones (to limit updates). Therefore, I needed a way to read my old generated feed and compare it to the new feed - while ignoring things like PubDate that changes on every invocation. To do this I needed to read an existing feed - and what better way to do this, than when this Podcast package!

    Except, it didn't work... I left out some common Marshaling requirements such as XMLName-ing the nil-able fields of structs and had a lot of custom marshaling going on with the Formatted fields.

    With the changes to use the Marshaler and Unmarshaler interfaces, I needed to remove those "Formatted" fields. I think this was enough to warrant a full on code review and Major release bump to 2.x.

    A bit of Freedom Removed

    This unfortunately creates a delima: you no longer have full control over your Feed with the removal of the plaintext "Formatted" string fields from the structs, and the move to use Marshaler and Unmarshaler interfaces. Now, you must use the structs and set items with strongly-typed objects.

    To me, this is a no brainer - i want the Marshaler and Unmarshaler interfaces to handle compliant feeds as well as strongly-typed objects.

    But to others that wanted complete control over their feeds, with non-compliant specially "Formatted" fields such as their own PubDate format, this 2.x breaks that ability.

    I believe this is ok. But, I am open to discussions here.

    Too Strict on Enclosure.EnclosureType?

    Unmarshaling, where we had none before have you, is implemented in a very strict nature with this version.

    See the large comment block in enclosure.go line L122: https://github.com/eduncan911/podcast/compare/develop...feature-Enable-Unmarshaling-Support?expand=1#diff-b2f071ccd78abc2ac116c7a8665bde7eR122

    For now, I am keeping it strict. Feel free to discuss.

    enhancement 
    opened by eduncan911 1
Releases(v1.4.2)
  • v1.4.2(Feb 13, 2020)

    New to Go Modules, I think the previous setup with "required" was far too much. This removes all but what one required to actually use the module (plus another one for any tests).

    • Slim down Go Modules for consumers (#32)
    Source code(tar.gz)
    Source code(zip)
  • v1.4.1(Feb 13, 2020)

    Minor release that just updates the README (not exported API), and adds Fuzzing to all inputs. It does not change the underlying code.

    • Implement fuzz logic testing of exported funcs (#31)
    • Upgrade CICD Pipeline Tooling (#31)
    • Update documentation for 1.x and 2.3 (#31)
    • Allow godoc2ghmd to run without network (#31)
    Source code(tar.gz)
    Source code(zip)
  • v1.4.0(Feb 7, 2020)

    First release with Go Modules, a significant feature addition to a package - hence the bump in Minor to 1.4 going forward. The most critical thing to note in this update is that the vendor/ folder has been flushed out with more dependencies to allow for a true air-gapped testing setup (which I ran into during development on a new machine).

    Unless you were looking for Go Modules support in your pipelines, this is not a required upgrade as there was no underlying package changes.

    As always, 1.x branch keeps backwards compatibility.

    Also note that going forward, Go Modules is supporting the vendor/ folder, keeping it backwards compatible with 1.7+ versions.

    • Add C.I., Add Go Modules (#27)
    • Create FUNDING.yml (#23) (#24)
    Source code(tar.gz)
    Source code(zip)
  • v1.3.2(Feb 5, 2020)

    A big thanks to everyone who have opened and resolved a number of PRs to this repo. This release was long overdue due to life events that just delayed things on my side.

    You should upgrade to this version over all previous versions for misc bug and improvements.

    As always, the 1.x branch retains all backwards compatibility and does not break anything.

    • Correct count len of UTF8 strings (#9)
    • Implement duration parser (#8)
    • Fix Github and GoDocs Markdown (#14)
    • Move podcast.go Private Methods to Respected Files (#12)
    • Allow providing GUID on Podcast (#15)
    Source code(tar.gz)
    Source code(zip)
  • v1.3.1(Feb 5, 2020)

    Ran across a few edge cases that needed some updates to the code. You should upgrade to this version over all previous versions.

    As always, the 1.x branch retains all backwards compatibility and does not break anything.

    • increased itunes compliance after feedback from Apple:
    • specified what categories should be set with AddCategory().
    • enforced title and link as part of Image.
    • added Podcast.AddAtomLink() for more broad compliance to readers.
    Source code(tar.gz)
    Source code(zip)
  • v1.3.0(Feb 20, 2017)

    1.3.0

    • fixes Item.Duration being set incorrectly.
    • changed Item.AddEnclosure() parameter definition (Bytes not Seconds!).
    • added Item.AddDuration formatting and override.
    • added more documentation surrounding Item.Enclosure{}

    1.2.1

    • added Podcast.AddSubTitle() and truncating to 64 chars.
    • added a number of Guards to protect against empty fields.

    1.2.0

    • added Podcast.AddPubDate() and Podcast.AddLastBuildDate() overrides.
    • added Item.AddImage() to mask some cumbersome addition of IImage.
    • added Item.AddPubDate to simply datetime setters.
    • added more examples (mostly around Item struct).
    • tweaked some documentation.
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Feb 6, 2017)

    Release notes:

    • Enabling CDATA in ISummary fields for Podcast and Channel.

    In this release, the consumer can now enter up to 4000 characters of rich text in the CDATA field. See the examples in the GoDoc for Podcast.AddSummary() and Item.AddSummary().

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Feb 6, 2017)

    In this initial release, the code has been written and tested with 100% code coverage.

    Tag Versions

    This repo will follow the standard Major.Minor.Patch release tagging in that:

    • Major numbers will be breaking changes to the API.
    • Minor numbers will be additional features and enhancements to the existing API.
    • Revision numbers will be bug fixes or other misc tweaks to the existing features and API.

    GoLang does support tag versioning; but, it isn't simple. Better to just use the vendor/ approach and snapshot this repo in any case.

    Current Roadmap

    There are several TODOs which may break the initial API, such as:

    • enabling CDATA ISummary tags for channel and item elements.
    • clean-up of the API, to make it similar (e.g. change podcast.New() to not take so many arguments.
    • additional validation checking (e.g. change podcast.New() to return Podcast, error.
    • additional validation checking (enforce 255 characters max for all fields, except summaries (iTunes)

    If such breaking changes happen, the version numbers will be bumped accordingly.

    Source code(tar.gz)
    Source code(zip)
Owner
Eric Duncan
Contact via DM @ twitter.com/eduncan911
Eric Duncan
golang rss/atom generator library

gorilla/feeds feeds is a web feed generator library for generating RSS, Atom and JSON feeds from Go applications. Goals Provide a simple interface to

Gorilla Web Toolkit 631 Sep 19, 2022
Watches container registries for new and changed tags and creates an RSS feed for detected changes.

Tagwatch Watches container registries for new and changed tags and creates an RSS feed for detected changes. Configuration Tagwatch is configured thro

Wolfgang Popp 1 Jan 7, 2022
Parse RSS, Atom and JSON feeds in Go

gofeed The gofeed library is a robust feed parser that supports parsing both RSS, Atom and JSON feeds. The library provides a universal gofeed.Parser

null 2k Sep 29, 2022
This command line converts thuderbird's exported RSS .eml file to .html file

thunderbird-rss-html This command line tool converts .html to .epub with images fetching. Install > go get github.com/gonejack/thunderbird-rss-html Us

会有猫的 0 Dec 15, 2021
Colored RSS feeds in your console

RSS Console Feed Read colored rss feeds in your console Usage ./rss-console-feed

Joe Gasewicz 0 Dec 22, 2021
Go XML sitemap and sitemap index generator

Install go get github.com/turk/go-sitemap Example for sitemapindex func () main(c *gin.Context) { s := sitemap.NewSitemapIndex(c.Writer, true)

Suleyman Yilmaz 3 Jun 29, 2022
A codename generator meant for naming software releases.

codename-generator This library written in Golang generates a random code name meant for naming software releases if you run short of inspiration. Cur

Jonathan Gautheron 22 Jun 26, 2022
Lorem Ipsum Generator

Generate lorem ipsum for your project. ============= Usage import "lorem" Ranged generators These will generate a string with a variable number of ele

Derek Rhodes 37 Jul 28, 2022
🚩 TOC, zero configuration table of content generator for Markdown files, create table of contents from any Markdown file with ease.

toc toc TOC, table of content generator for Markdown files Table of Contents Table of Contents Usage Installation Packages Arch Linux Homebrew Docker

Yagiz Degirmenci 87 Jul 27, 2022
GR 4 - Wow class generator

Wow class generator Simple generator to create maps in Go using warcraft logs for Class and Spec Names. It generates : A dict of current wow classes a

Hetic MT Promotion 2021 0 Nov 5, 2021
Simple text-to-ascii-art generator

Simple text-to-ascii-art generator

Topi Kettunen 0 Nov 17, 2021
🧑‍💻 Go XML generator without Structs™

exml ??‍?? Go XML generator without Structs™ Package exml allows XML documents to be generated without the usage of structs or maps. It is not intende

Victor 2 May 16, 2022
[Crawler/Scraper for Golang]🕷A lightweight distributed friendly Golang crawler framework.一个轻量的分布式友好的 Golang 爬虫框架。

Goribot 一个分布式友好的轻量的 Golang 爬虫框架。 完整文档 | Document !! Warning !! Goribot 已经被迁移到 Gospider|github.com/zhshch2002/gospider。修复了一些调度问题并分离了网络请求部分到另一个仓库。此仓库会继续

null 208 Aug 26, 2022
Pagser is a simple, extensible, configurable parse and deserialize html page to struct based on goquery and struct tags for golang crawler

Pagser Pagser inspired by page parser。 Pagser is a simple, extensible, configurable parse and deserialize html page to struct based on goquery and str

foolin 69 Sep 14, 2022
golang 在线预览word,excel,pdf,MarkDown(Online Preview Word,Excel,PPT,PDF,Image by Golang)

Go View File 在线体验地址 http://39.97.98.75:8082/view/upload (不会经常更新,保留最基本的预览功能。服务器配置较低,如果出现链接超时请等待几秒刷新重试,或者换Chrome) 目前已经完成 docker部署 (不用为运行环境烦恼) Wor

CZC 71 Sep 19, 2022
Elegant Scraper and Crawler Framework for Golang

Colly Lightning Fast and Elegant Scraping Framework for Gophers Colly provides a clean interface to write any kind of crawler/scraper/spider. With Col

Colly 17.8k Oct 1, 2022
omniparser: a native Golang ETL streaming parser and transform library for CSV, JSON, XML, EDI, text, etc.

omniparser Omniparser is a native Golang ETL parser that ingests input data of various formats (CSV, txt, fixed length/width, XML, EDI/X12/EDIFACT, JS

JF Technology 494 Sep 27, 2022
agrep-like fuzzy matching, but made faster using Golang and precomputation.

goagrep There are situations where you want to take the user's input and match a primary key in a database. But, immediately a problem is introduced:

Zack 41 Nov 30, 2021
Golang metrics for calculating string similarity and other string utility functions

strutil strutil provides string metrics for calculating string similarity as well as other string utility functions. Full documentation can be found a

Adrian-George Bostan 122 Sep 22, 2022