Code Rewrite

main
Uriel Fanelli 2025-05-04 02:55:08 +02:00
parent 746882138e
commit 2d397d1382
21 changed files with 1401 additions and 71 deletions

1
go.mod
View File

@ -5,4 +5,5 @@ go 1.21.0
require ( require (
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0
) )

8
go.sum
View File

@ -2,3 +2,11 @@ github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 h1:OtSeLS5y0Uy01jaKK4m
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E= github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=

22
main.go
View File

@ -15,6 +15,10 @@ var (
audioDir = os.Getenv("PODCAST_AUDIO_DIR") // Es: "/data/podcast/audio" audioDir = os.Getenv("PODCAST_AUDIO_DIR") // Es: "/data/podcast/audio"
coversDir = os.Getenv("PODCAST_COVERS_DIR") // Es: "/data/podcast/covers" coversDir = os.Getenv("PODCAST_COVERS_DIR") // Es: "/data/podcast/covers"
podTitle = os.Getenv("PODCAST_TITLE") // Es: "Il Mio Podcast" podTitle = os.Getenv("PODCAST_TITLE") // Es: "Il Mio Podcast"
podAuthor = os.Getenv("PODCAST_AUTHOR") // Es: "Il Mio Podcast"
podRights = os.Getenv("PODCAST_COPYRIGHT") // Es: "Il Mio Podcast"
podLogo = os.Getenv("PODCAST_LOGO") // Es: "Il Mio Podcast"
podDesc = os.Getenv("PODCAST_DESCRIPTION") // Es: "Il Mio Podcast"
) )
func init() { func init() {
@ -28,6 +32,10 @@ func init() {
{"PODCAST_AUDIO_DIR", audioDir}, {"PODCAST_AUDIO_DIR", audioDir},
{"PODCAST_COVERS_DIR", coversDir}, {"PODCAST_COVERS_DIR", coversDir},
{"PODCAST_TITLE", podTitle}, {"PODCAST_TITLE", podTitle},
{"PODCAST_AUTHOR", podAuthor},
{"PODCAST_COPYRIGHT", podRights},
{"PODCAST_LOGO", podLogo},
{"PODCAST_DESCRIPTION", podDesc},
} { } {
if v.value == "" { if v.value == "" {
log.Fatalf("Variabile d'ambiente mancante: %s", v.name) log.Fatalf("Variabile d'ambiente mancante: %s", v.name)
@ -40,19 +48,23 @@ func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Verifica se la richiesta è per un file audio o cover // Verifica se la richiesta è per un file audio o cover
requestedFile := filepath.Clean(r.URL.Path) requestedFile := filepath.Clean(r.URL.Path)
isAudio := strings.HasPrefix(requestedFile, "/audio/") isAudio := strings.HasPrefix(requestedFile, "/audio/") && strings.HasSuffix(requestedFile, ".mp3")
isCover := strings.HasPrefix(requestedFile, "/covers/") isCover := strings.HasPrefix(requestedFile, "/covers/") && strings.HasSuffix(requestedFile, ".jpg")
// logghiamo che succede isLogo := strings.HasPrefix(requestedFile, "/cover.jpg") && strings.HasSuffix(requestedFile, ".jpg")
log.Println("Richiesta da: ", r.UserAgent(), " per ", r.RequestURI)
if isAudio || isCover { if isAudio || isCover || isLogo {
// Servi il file richiesto // Servi il file richiesto
http.ServeFile(w, r, filepath.Join(".", requestedFile)) http.ServeFile(w, r, filepath.Join(".", requestedFile))
// logghiamo che succede
log.Println("Richiesta da: ", r.UserAgent(), " per ", r.RequestURI)
return return
} }
// Altrimenti servi sempre l'RSS // Altrimenti servi sempre l'RSS
if err := generateRSS(); err != nil { if err := generateRSS(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
// logghiamo che succede
log.Println("Richiesta da: ", r.UserAgent(), " per ", r.RequestURI, " --> feeds.xml ")
return return
} }
http.ServeFile(w, r, "feed.xml") http.ServeFile(w, r, "feed.xml")

55
pod.go
View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
@ -18,6 +17,7 @@ type Episode struct {
File string File string
Cover string Cover string
PubDate string PubDate string
Description string
Artist string Artist string
Size int64 Size int64
} }
@ -32,54 +32,6 @@ func generateUUIDFromString(input string) uuid.UUID {
return uuid.NewSHA1(namespace, []byte(input)) return uuid.NewSHA1(namespace, []byte(input))
} }
func generateRSS() error {
rssLock.Lock()
defer rssLock.Unlock()
episodes, err := scanEpisodes()
if err != nil {
return err
}
rss := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
xmlns:podcast="https://podcastindex.org/namespace/1.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<description>%s</description>
<title>%s</title>
<link>%s</link>`, podTitle, podTitle, baseURL)
for _, ep := range episodes {
rss += fmt.Sprintf(`
<item>
<title>%s</title>
<enclosure url="%s/audio/%s" type="audio/mpeg" length="%d"/>
<pubDate>%s</pubDate>
`, ep.Title, baseURL, filepath.Base(ep.File), ep.Size, ep.PubDate)
if ep.Cover != "" {
rss += fmt.Sprintf(`<itunes:image href="%s/covers/%s"/>`, baseURL, ep.Cover)
}
if ep.Artist != "" {
rss += fmt.Sprintf(`
<itunes:author>%s</itunes:author>`, ep.Artist)
}
rss += fmt.Sprintf(`<link>%s/audio/%s</link>`, baseURL, filepath.Base(ep.File))
rss += fmt.Sprintf(`<guid isPermaLink="false">%s</guid>`, generateUUIDFromString(ep.Title).String())
rss += fmt.Sprintf(`<description>%s</description>`, ep.Title)
rss += "\n </item>"
}
rss += "\n</channel>\n</rss>"
return os.WriteFile("feed.xml", []byte(rss), 0644)
}
func scanEpisodes() ([]Episode, error) { func scanEpisodes() ([]Episode, error) {
var episodes []Episode var episodes []Episode
@ -107,12 +59,17 @@ func scanEpisodes() ([]Episode, error) {
ep := Episode{ ep := Episode{
Title: meta.Title(), Title: meta.Title(),
Artist: meta.Artist(), Artist: meta.Artist(),
Description: meta.Comment(),
File: baseName + ".mp3", File: baseName + ".mp3",
Cover: baseName + ".jpg", Cover: baseName + ".jpg",
PubDate: info.ModTime().Format(time.RFC1123), PubDate: info.ModTime().Format(time.RFC1123),
Size: info.Size(), Size: info.Size(),
} }
if ep.Description == "" {
ep.Description = ep.Title
}
episodes = append(episodes, ep) episodes = append(episodes, ep)
return nil return nil
}) })

63
rss-xml.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/gorilla/feeds"
)
func generateRSS() error {
rssLock.Lock()
defer rssLock.Unlock()
episodes, err := scanEpisodes()
if err != nil {
return err
}
// rss := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
// <rss version="2.0"
// xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
// xmlns:podcast="https://podcastindex.org/namespace/1.0"
// xmlns:atom="http://www.w3.org/2005/Atom"
// xmlns:content="http://purl.org/rss/1.0/modules/content/">
// <channel>
// <description>%s</description>
// <title>%s</title>
// <link>%s</link>`, podTitle, podTitle, baseURL)
feed := &feeds.Feed{
Title: podTitle,
Link: &feeds.Link{Href: baseURL},
Description: podDesc,
Author: &feeds.Author{Name: podAuthor},
Created: time.Now(),
Copyright: podRights,
}
for _, ep := range episodes {
epBaseUrl := baseURL + "/audio/" + filepath.Base(ep.File)
feed.Add(&feeds.Item{
Title: ep.Title,
Link: &feeds.Link{Href: epBaseUrl},
Description: ep.Description,
Enclosure: &feeds.Enclosure{
Url: epBaseUrl,
Length: fmt.Sprintf("%d", ep.Size),
Type: "audio/mpeg",
},
Created: time.Now(),
})
}
// Genera RSS
rss, _ := feed.ToRss()
return os.WriteFile("feed.xml", []byte(rss), 0644)
}

BIN
u-pod

Binary file not shown.

20
vendor/github.com/gorilla/feeds/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,20 @@
; https://editorconfig.org/
root = true
[*]
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
indent_style = tab
indent_size = 4
[*.md]
indent_size = 4
trim_trailing_whitespace = false
eclint_indent_style = unset

1
vendor/github.com/gorilla/feeds/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
coverage.coverprofile

28
vendor/github.com/gorilla/feeds/LICENSE generated vendored Normal file
View File

@ -0,0 +1,28 @@
Copyright (c) 2023 The Gorilla Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

34
vendor/github.com/gorilla/feeds/Makefile generated vendored Normal file
View File

@ -0,0 +1,34 @@
GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')
GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest
GO_SEC=$(shell which gosec 2> /dev/null || echo '')
GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest
GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '')
GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest
.PHONY: golangci-lint
golangci-lint:
$(if $(GO_LINT), ,go install $(GO_LINT_URI))
@echo "##### Running golangci-lint"
golangci-lint run -v
.PHONY: gosec
gosec:
$(if $(GO_SEC), ,go install $(GO_SEC_URI))
@echo "##### Running gosec"
gosec ./...
.PHONY: govulncheck
govulncheck:
$(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI))
@echo "##### Running govulncheck"
govulncheck ./...
.PHONY: verify
verify: golangci-lint gosec govulncheck
.PHONY: test
test:
@echo "##### Running tests"
go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./...

198
vendor/github.com/gorilla/feeds/README.md generated vendored Normal file
View File

@ -0,0 +1,198 @@
## gorilla/feeds
![testing](https://github.com/gorilla/feeds/actions/workflows/test.yml/badge.svg)
[![codecov](https://codecov.io/github/gorilla/feeds/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/feeds)
[![godoc](https://godoc.org/github.com/gorilla/feeds?status.svg)](https://godoc.org/github.com/gorilla/feeds)
[![sourcegraph](https://sourcegraph.com/github.com/gorilla/feeds/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/feeds?badge)
![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5)
feeds is a web feed generator library for generating RSS, Atom and JSON feeds from Go
applications.
### Goals
* Provide a simple interface to create both Atom & RSS 2.0 feeds
* Full support for [Atom][atom], [RSS 2.0][rss], and [JSON Feed Version 1][jsonfeed] spec elements
* Ability to modify particulars for each spec
[atom]: https://tools.ietf.org/html/rfc4287
[rss]: http://www.rssboard.org/rss-specification
[jsonfeed]: https://jsonfeed.org/version/1.1
### Usage
```go
package main
import (
"fmt"
"log"
"time"
"github.com/gorilla/feeds"
)
func main() {
now := time.Now()
feed := &feeds.Feed{
Title: "jmoiron.net blog",
Link: &feeds.Link{Href: "http://jmoiron.net/blog"},
Description: "discussion about tech, footie, photos",
Author: &feeds.Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
Created: now,
}
feed.Items = []*feeds.Item{
&feeds.Item{
Title: "Limiting Concurrency in Go",
Link: &feeds.Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
Description: "A discussion on controlled parallelism in golang",
Author: &feeds.Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
Created: now,
},
&feeds.Item{
Title: "Logic-less Template Redux",
Link: &feeds.Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
Description: "More thoughts on logicless templates",
Created: now,
},
&feeds.Item{
Title: "Idiomatic Code Reuse in Go",
Link: &feeds.Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
Description: "How to use interfaces <em>effectively</em>",
Created: now,
},
}
atom, err := feed.ToAtom()
if err != nil {
log.Fatal(err)
}
rss, err := feed.ToRss()
if err != nil {
log.Fatal(err)
}
json, err := feed.ToJSON()
if err != nil {
log.Fatal(err)
}
fmt.Println(atom, "\n", rss, "\n", json)
}
```
Outputs:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>jmoiron.net blog</title>
<link href="http://jmoiron.net/blog"></link>
<id>http://jmoiron.net/blog</id>
<updated>2013-01-16T03:26:01-05:00</updated>
<summary>discussion about tech, footie, photos</summary>
<entry>
<title>Limiting Concurrency in Go</title>
<link href="http://jmoiron.net/blog/limiting-concurrency-in-go/"></link>
<updated>2013-01-16T03:26:01-05:00</updated>
<id>tag:jmoiron.net,2013-01-16:/blog/limiting-concurrency-in-go/</id>
<summary type="html">A discussion on controlled parallelism in golang</summary>
<author>
<name>Jason Moiron</name>
<email>jmoiron@jmoiron.net</email>
</author>
</entry>
<entry>
<title>Logic-less Template Redux</title>
<link href="http://jmoiron.net/blog/logicless-template-redux/"></link>
<updated>2013-01-16T03:26:01-05:00</updated>
<id>tag:jmoiron.net,2013-01-16:/blog/logicless-template-redux/</id>
<summary type="html">More thoughts on logicless templates</summary>
<author></author>
</entry>
<entry>
<title>Idiomatic Code Reuse in Go</title>
<link href="http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"></link>
<updated>2013-01-16T03:26:01-05:00</updated>
<id>tag:jmoiron.net,2013-01-16:/blog/idiomatic-code-reuse-in-go/</id>
<summary type="html">How to use interfaces &lt;em&gt;effectively&lt;/em&gt;</summary>
<author></author>
</entry>
</feed>
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>jmoiron.net blog</title>
<link>http://jmoiron.net/blog</link>
<description>discussion about tech, footie, photos</description>
<managingEditor>jmoiron@jmoiron.net (Jason Moiron)</managingEditor>
<pubDate>2013-01-16T03:22:24-05:00</pubDate>
<item>
<title>Limiting Concurrency in Go</title>
<link>http://jmoiron.net/blog/limiting-concurrency-in-go/</link>
<description>A discussion on controlled parallelism in golang</description>
<pubDate>2013-01-16T03:22:24-05:00</pubDate>
</item>
<item>
<title>Logic-less Template Redux</title>
<link>http://jmoiron.net/blog/logicless-template-redux/</link>
<description>More thoughts on logicless templates</description>
<pubDate>2013-01-16T03:22:24-05:00</pubDate>
</item>
<item>
<title>Idiomatic Code Reuse in Go</title>
<link>http://jmoiron.net/blog/idiomatic-code-reuse-in-go/</link>
<description>How to use interfaces &lt;em&gt;effectively&lt;/em&gt;</description>
<pubDate>2013-01-16T03:22:24-05:00</pubDate>
</item>
</channel>
</rss>
{
"version": "https://jsonfeed.org/version/1.1",
"title": "jmoiron.net blog",
"home_page_url": "http://jmoiron.net/blog",
"description": "discussion about tech, footie, photos",
"author": {
"name": "Jason Moiron"
},
"authors": [
{
"name": "Jason Moiron"
}
],
"items": [
{
"id": "",
"url": "http://jmoiron.net/blog/limiting-concurrency-in-go/",
"title": "Limiting Concurrency in Go",
"summary": "A discussion on controlled parallelism in golang",
"date_published": "2013-01-16T03:22:24.530817846-05:00",
"author": {
"name": "Jason Moiron"
},
"authors": [
{
"name": "Jason Moiron"
}
]
},
{
"id": "",
"url": "http://jmoiron.net/blog/logicless-template-redux/",
"title": "Logic-less Template Redux",
"summary": "More thoughts on logicless templates",
"date_published": "2013-01-16T03:22:24.530817846-05:00"
},
{
"id": "",
"url": "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/",
"title": "Idiomatic Code Reuse in Go",
"summary": "How to use interfaces \u003cem\u003eeffectively\u003c/em\u003e",
"date_published": "2013-01-16T03:22:24.530817846-05:00"
}
]
}
```

178
vendor/github.com/gorilla/feeds/atom.go generated vendored Normal file
View File

@ -0,0 +1,178 @@
package feeds
import (
"encoding/xml"
"fmt"
"net/url"
"time"
)
// Generates Atom feed as XML
const ns = "http://www.w3.org/2005/Atom"
type AtomPerson struct {
Name string `xml:"name,omitempty"`
Uri string `xml:"uri,omitempty"`
Email string `xml:"email,omitempty"`
}
type AtomSummary struct {
XMLName xml.Name `xml:"summary"`
Content string `xml:",chardata"`
Type string `xml:"type,attr"`
}
type AtomContent struct {
XMLName xml.Name `xml:"content"`
Content string `xml:",chardata"`
Type string `xml:"type,attr"`
}
type AtomAuthor struct {
XMLName xml.Name `xml:"author"`
AtomPerson
}
type AtomContributor struct {
XMLName xml.Name `xml:"contributor"`
AtomPerson
}
type AtomEntry struct {
XMLName xml.Name `xml:"entry"`
Xmlns string `xml:"xmlns,attr,omitempty"`
Title string `xml:"title"` // required
Updated string `xml:"updated"` // required
Id string `xml:"id"` // required
Category string `xml:"category,omitempty"`
Content *AtomContent
Rights string `xml:"rights,omitempty"`
Source string `xml:"source,omitempty"`
Published string `xml:"published,omitempty"`
Contributor *AtomContributor
Links []AtomLink // required if no child 'content' elements
Summary *AtomSummary // required if content has src or content is base64
Author *AtomAuthor // required if feed lacks an author
}
// Multiple links with different rel can coexist
type AtomLink struct {
//Atom 1.0 <link rel="enclosure" type="audio/mpeg" title="MP3" href="http://www.example.org/myaudiofile.mp3" length="1234" />
XMLName xml.Name `xml:"link"`
Href string `xml:"href,attr"`
Rel string `xml:"rel,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
Length string `xml:"length,attr,omitempty"`
}
type AtomFeed struct {
XMLName xml.Name `xml:"feed"`
Xmlns string `xml:"xmlns,attr"`
Title string `xml:"title"` // required
Id string `xml:"id"` // required
Updated string `xml:"updated"` // required
Category string `xml:"category,omitempty"`
Icon string `xml:"icon,omitempty"`
Logo string `xml:"logo,omitempty"`
Rights string `xml:"rights,omitempty"` // copyright used
Subtitle string `xml:"subtitle,omitempty"`
Link *AtomLink
Author *AtomAuthor `xml:"author,omitempty"`
Contributor *AtomContributor
Entries []*AtomEntry `xml:"entry"`
}
type Atom struct {
*Feed
}
func newAtomEntry(i *Item) *AtomEntry {
id := i.Id
link := i.Link
if link == nil {
link = &Link{}
}
if len(id) == 0 {
// if there's no id set, try to create one, either from data or just a uuid
if len(link.Href) > 0 && (!i.Created.IsZero() || !i.Updated.IsZero()) {
dateStr := anyTimeFormat("2006-01-02", i.Updated, i.Created)
host, path := link.Href, "/invalid.html"
if url, err := url.Parse(link.Href); err == nil {
host, path = url.Host, url.Path
}
id = fmt.Sprintf("tag:%s,%s:%s", host, dateStr, path)
} else {
id = "urn:uuid:" + NewUUID().String()
}
}
var name, email string
if i.Author != nil {
name, email = i.Author.Name, i.Author.Email
}
link_rel := link.Rel
if link_rel == "" {
link_rel = "alternate"
}
x := &AtomEntry{
Title: i.Title,
Links: []AtomLink{{Href: link.Href, Rel: link_rel, Type: link.Type}},
Id: id,
Updated: anyTimeFormat(time.RFC3339, i.Updated, i.Created),
}
// if there's a description, assume it's html
if len(i.Description) > 0 {
x.Summary = &AtomSummary{Content: i.Description, Type: "html"}
}
// if there's a content, assume it's html
if len(i.Content) > 0 {
x.Content = &AtomContent{Content: i.Content, Type: "html"}
}
if i.Enclosure != nil && link_rel != "enclosure" {
x.Links = append(x.Links, AtomLink{Href: i.Enclosure.Url, Rel: "enclosure", Type: i.Enclosure.Type, Length: i.Enclosure.Length})
}
if len(name) > 0 || len(email) > 0 {
x.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: name, Email: email}}
}
return x
}
// create a new AtomFeed with a generic Feed struct's data
func (a *Atom) AtomFeed() *AtomFeed {
updated := anyTimeFormat(time.RFC3339, a.Updated, a.Created)
link := a.Link
if link == nil {
link = &Link{}
}
feed := &AtomFeed{
Xmlns: ns,
Title: a.Title,
Link: &AtomLink{Href: link.Href, Rel: link.Rel},
Subtitle: a.Description,
Id: link.Href,
Updated: updated,
Rights: a.Copyright,
}
if a.Author != nil {
feed.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: a.Author.Name, Email: a.Author.Email}}
}
for _, e := range a.Items {
feed.Entries = append(feed.Entries, newAtomEntry(e))
}
return feed
}
// FeedXml returns an XML-Ready object for an Atom object
func (a *Atom) FeedXml() interface{} {
return a.AtomFeed()
}
// FeedXml returns an XML-ready object for an AtomFeed object
func (a *AtomFeed) FeedXml() interface{} {
return a
}

73
vendor/github.com/gorilla/feeds/doc.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
/*
Syndication (feed) generator library for golang.
Installing
go get github.com/gorilla/feeds
Feeds provides a simple, generic Feed interface with a generic Item object as well as RSS, Atom and JSON Feed specific RssFeed, AtomFeed and JSONFeed objects which allow access to all of each spec's defined elements.
Examples
Create a Feed and some Items in that feed using the generic interfaces:
import (
"time"
. "github.com/gorilla/feeds"
)
now = time.Now()
feed := &Feed{
Title: "jmoiron.net blog",
Link: &Link{Href: "http://jmoiron.net/blog"},
Description: "discussion about tech, footie, photos",
Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
Created: now,
Copyright: "This work is copyright © Benjamin Button",
}
feed.Items = []*Item{
&Item{
Title: "Limiting Concurrency in Go",
Link: &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
Description: "A discussion on controlled parallelism in golang",
Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
Created: now,
},
&Item{
Title: "Logic-less Template Redux",
Link: &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
Description: "More thoughts on logicless templates",
Created: now,
},
&Item{
Title: "Idiomatic Code Reuse in Go",
Link: &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
Description: "How to use interfaces <em>effectively</em>",
Created: now,
},
}
From here, you can output Atom, RSS, or JSON Feed versions of this feed easily
atom, err := feed.ToAtom()
rss, err := feed.ToRss()
json, err := feed.ToJSON()
You can also get access to the underlying objects that feeds uses to export its XML
atomFeed := (&Atom{Feed: feed}).AtomFeed()
rssFeed := (&Rss{Feed: feed}).RssFeed()
jsonFeed := (&JSON{Feed: feed}).JSONFeed()
From here, you can modify or add each syndication's specific fields before outputting
atomFeed.Subtitle = "plays the blues"
atom, err := ToXML(atomFeed)
rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)"
rss, err := ToXML(rssFeed)
jsonFeed.NextUrl = "https://www.example.com/feed.json?page=2"
json, err := jsonFeed.ToJSON()
*/
package feeds

146
vendor/github.com/gorilla/feeds/feed.go generated vendored Normal file
View File

@ -0,0 +1,146 @@
package feeds
import (
"encoding/json"
"encoding/xml"
"io"
"sort"
"time"
)
type Link struct {
Href, Rel, Type, Length string
}
type Author struct {
Name, Email string
}
type Image struct {
Url, Title, Link string
Width, Height int
}
type Enclosure struct {
Url, Length, Type string
}
type Item struct {
Title string
Link *Link
Source *Link
Author *Author
Description string // used as description in rss, summary in atom
Id string // used as guid in rss, id in atom
IsPermaLink string // an optional parameter for guid in rss
Updated time.Time
Created time.Time
Enclosure *Enclosure
Content string
}
type Feed struct {
Title string
Link *Link
Description string
Author *Author
Updated time.Time
Created time.Time
Id string
Subtitle string
Items []*Item
Copyright string
Image *Image
}
// add a new Item to a Feed
func (f *Feed) Add(item *Item) {
f.Items = append(f.Items, item)
}
// returns the first non-zero time formatted as a string or ""
func anyTimeFormat(format string, times ...time.Time) string {
for _, t := range times {
if !t.IsZero() {
return t.Format(format)
}
}
return ""
}
// interface used by ToXML to get a object suitable for exporting XML.
type XmlFeed interface {
FeedXml() interface{}
}
// turn a feed object (either a Feed, AtomFeed, or RssFeed) into xml
// returns an error if xml marshaling fails
func ToXML(feed XmlFeed) (string, error) {
x := feed.FeedXml()
data, err := xml.MarshalIndent(x, "", " ")
if err != nil {
return "", err
}
// strip empty line from default xml header
s := xml.Header[:len(xml.Header)-1] + string(data)
return s, nil
}
// WriteXML writes a feed object (either a Feed, AtomFeed, or RssFeed) as XML into
// the writer. Returns an error if XML marshaling fails.
func WriteXML(feed XmlFeed, w io.Writer) error {
x := feed.FeedXml()
// write default xml header, without the newline
if _, err := w.Write([]byte(xml.Header[:len(xml.Header)-1])); err != nil {
return err
}
e := xml.NewEncoder(w)
e.Indent("", " ")
return e.Encode(x)
}
// creates an Atom representation of this feed
func (f *Feed) ToAtom() (string, error) {
a := &Atom{f}
return ToXML(a)
}
// WriteAtom writes an Atom representation of this feed to the writer.
func (f *Feed) WriteAtom(w io.Writer) error {
return WriteXML(&Atom{f}, w)
}
// creates an Rss representation of this feed
func (f *Feed) ToRss() (string, error) {
r := &Rss{f}
return ToXML(r)
}
// WriteRss writes an RSS representation of this feed to the writer.
func (f *Feed) WriteRss(w io.Writer) error {
return WriteXML(&Rss{f}, w)
}
// ToJSON creates a JSON Feed representation of this feed
func (f *Feed) ToJSON() (string, error) {
j := &JSON{f}
return j.ToJSON()
}
// WriteJSON writes an JSON representation of this feed to the writer.
func (f *Feed) WriteJSON(w io.Writer) error {
j := &JSON{f}
feed := j.JSONFeed()
e := json.NewEncoder(w)
e.SetIndent("", " ")
return e.Encode(feed)
}
// Sort sorts the Items in the feed with the given less function.
func (f *Feed) Sort(less func(a, b *Item) bool) {
lessFunc := func(i, j int) bool {
return less(f.Items[i], f.Items[j])
}
sort.SliceStable(f.Items, lessFunc)
}

190
vendor/github.com/gorilla/feeds/json.go generated vendored Normal file
View File

@ -0,0 +1,190 @@
package feeds
import (
"encoding/json"
"strings"
"time"
)
const jsonFeedVersion = "https://jsonfeed.org/version/1.1"
// JSONAuthor represents the author of the feed or of an individual item
// in the feed
type JSONAuthor struct {
Name string `json:"name,omitempty"`
Url string `json:"url,omitempty"`
Avatar string `json:"avatar,omitempty"`
}
// JSONAttachment represents a related resource. Podcasts, for instance, would
// include an attachment thats an audio or video file.
type JSONAttachment struct {
Url string `json:"url,omitempty"`
MIMEType string `json:"mime_type,omitempty"`
Title string `json:"title,omitempty"`
Size int32 `json:"size,omitempty"`
Duration time.Duration `json:"duration_in_seconds,omitempty"`
}
// MarshalJSON implements the json.Marshaler interface.
// The Duration field is marshaled in seconds, all other fields are marshaled
// based upon the definitions in struct tags.
func (a *JSONAttachment) MarshalJSON() ([]byte, error) {
type EmbeddedJSONAttachment JSONAttachment
return json.Marshal(&struct {
Duration float64 `json:"duration_in_seconds,omitempty"`
*EmbeddedJSONAttachment
}{
EmbeddedJSONAttachment: (*EmbeddedJSONAttachment)(a),
Duration: a.Duration.Seconds(),
})
}
// UnmarshalJSON implements the json.Unmarshaler interface.
// The Duration field is expected to be in seconds, all other field types
// match the struct definition.
func (a *JSONAttachment) UnmarshalJSON(data []byte) error {
type EmbeddedJSONAttachment JSONAttachment
var raw struct {
Duration float64 `json:"duration_in_seconds,omitempty"`
*EmbeddedJSONAttachment
}
raw.EmbeddedJSONAttachment = (*EmbeddedJSONAttachment)(a)
err := json.Unmarshal(data, &raw)
if err != nil {
return err
}
if raw.Duration > 0 {
nsec := int64(raw.Duration * float64(time.Second))
raw.EmbeddedJSONAttachment.Duration = time.Duration(nsec)
}
return nil
}
// JSONItem represents a single entry/post for the feed.
type JSONItem struct {
Id string `json:"id"`
Url string `json:"url,omitempty"`
ExternalUrl string `json:"external_url,omitempty"`
Title string `json:"title,omitempty"`
ContentHTML string `json:"content_html,omitempty"`
ContentText string `json:"content_text,omitempty"`
Summary string `json:"summary,omitempty"`
Image string `json:"image,omitempty"`
BannerImage string `json:"banner_,omitempty"`
PublishedDate *time.Time `json:"date_published,omitempty"`
ModifiedDate *time.Time `json:"date_modified,omitempty"`
Author *JSONAuthor `json:"author,omitempty"` // deprecated in JSON Feed v1.1, keeping for backwards compatibility
Authors []*JSONAuthor `json:"authors,omitempty"`
Tags []string `json:"tags,omitempty"`
Attachments []JSONAttachment `json:"attachments,omitempty"`
}
// JSONHub describes an endpoint that can be used to subscribe to real-time
// notifications from the publisher of this feed.
type JSONHub struct {
Type string `json:"type"`
Url string `json:"url"`
}
// JSONFeed represents a syndication feed in the JSON Feed Version 1 format.
// Matching the specification found here: https://jsonfeed.org/version/1.
type JSONFeed struct {
Version string `json:"version"`
Title string `json:"title"`
Language string `json:"language,omitempty"`
HomePageUrl string `json:"home_page_url,omitempty"`
FeedUrl string `json:"feed_url,omitempty"`
Description string `json:"description,omitempty"`
UserComment string `json:"user_comment,omitempty"`
NextUrl string `json:"next_url,omitempty"`
Icon string `json:"icon,omitempty"`
Favicon string `json:"favicon,omitempty"`
Author *JSONAuthor `json:"author,omitempty"` // deprecated in JSON Feed v1.1, keeping for backwards compatibility
Authors []*JSONAuthor `json:"authors,omitempty"`
Expired *bool `json:"expired,omitempty"`
Hubs []*JSONHub `json:"hubs,omitempty"`
Items []*JSONItem `json:"items,omitempty"`
}
// JSON is used to convert a generic Feed to a JSONFeed.
type JSON struct {
*Feed
}
// ToJSON encodes f into a JSON string. Returns an error if marshalling fails.
func (f *JSON) ToJSON() (string, error) {
return f.JSONFeed().ToJSON()
}
// ToJSON encodes f into a JSON string. Returns an error if marshalling fails.
func (f *JSONFeed) ToJSON() (string, error) {
data, err := json.MarshalIndent(f, "", " ")
if err != nil {
return "", err
}
return string(data), nil
}
// JSONFeed creates a new JSONFeed with a generic Feed struct's data.
func (f *JSON) JSONFeed() *JSONFeed {
feed := &JSONFeed{
Version: jsonFeedVersion,
Title: f.Title,
Description: f.Description,
}
if f.Link != nil {
feed.HomePageUrl = f.Link.Href
}
if f.Author != nil {
author := &JSONAuthor{
Name: f.Author.Name,
}
feed.Author = author
feed.Authors = []*JSONAuthor{author}
}
for _, e := range f.Items {
feed.Items = append(feed.Items, newJSONItem(e))
}
return feed
}
func newJSONItem(i *Item) *JSONItem {
item := &JSONItem{
Id: i.Id,
Title: i.Title,
Summary: i.Description,
ContentHTML: i.Content,
}
if i.Link != nil {
item.Url = i.Link.Href
}
if i.Source != nil {
item.ExternalUrl = i.Source.Href
}
if i.Author != nil {
author := &JSONAuthor{
Name: i.Author.Name,
}
item.Author = author
item.Authors = []*JSONAuthor{author}
}
if !i.Created.IsZero() {
item.PublishedDate = &i.Created
}
if !i.Updated.IsZero() {
item.ModifiedDate = &i.Updated
}
if i.Enclosure != nil && strings.HasPrefix(i.Enclosure.Type, "image/") {
item.Image = i.Enclosure.Url
}
return item
}

183
vendor/github.com/gorilla/feeds/rss.go generated vendored Normal file
View File

@ -0,0 +1,183 @@
package feeds
// rss support
// validation done according to spec here:
// http://cyber.law.harvard.edu/rss/rss.html
import (
"encoding/xml"
"fmt"
"time"
)
// private wrapper around the RssFeed which gives us the <rss>..</rss> xml
type RssFeedXml struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
ContentNamespace string `xml:"xmlns:content,attr"`
Channel *RssFeed
}
type RssContent struct {
XMLName xml.Name `xml:"content:encoded"`
Content string `xml:",cdata"`
}
type RssImage struct {
XMLName xml.Name `xml:"image"`
Url string `xml:"url"`
Title string `xml:"title"`
Link string `xml:"link"`
Width int `xml:"width,omitempty"`
Height int `xml:"height,omitempty"`
}
type RssTextInput struct {
XMLName xml.Name `xml:"textInput"`
Title string `xml:"title"`
Description string `xml:"description"`
Name string `xml:"name"`
Link string `xml:"link"`
}
type RssFeed struct {
XMLName xml.Name `xml:"channel"`
Title string `xml:"title"` // required
Link string `xml:"link"` // required
Description string `xml:"description"` // required
Language string `xml:"language,omitempty"`
Copyright string `xml:"copyright,omitempty"`
ManagingEditor string `xml:"managingEditor,omitempty"` // Author used
WebMaster string `xml:"webMaster,omitempty"`
PubDate string `xml:"pubDate,omitempty"` // created or updated
LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used
Category string `xml:"category,omitempty"`
Generator string `xml:"generator,omitempty"`
Docs string `xml:"docs,omitempty"`
Cloud string `xml:"cloud,omitempty"`
Ttl int `xml:"ttl,omitempty"`
Rating string `xml:"rating,omitempty"`
SkipHours string `xml:"skipHours,omitempty"`
SkipDays string `xml:"skipDays,omitempty"`
Image *RssImage
TextInput *RssTextInput
Items []*RssItem `xml:"item"`
}
type RssItem struct {
XMLName xml.Name `xml:"item"`
Title string `xml:"title"` // required
Link string `xml:"link"` // required
Description string `xml:"description"` // required
Content *RssContent
Author string `xml:"author,omitempty"`
Category string `xml:"category,omitempty"`
Comments string `xml:"comments,omitempty"`
Enclosure *RssEnclosure
Guid *RssGuid // Id used
PubDate string `xml:"pubDate,omitempty"` // created or updated
Source string `xml:"source,omitempty"`
}
type RssEnclosure struct {
//RSS 2.0 <enclosure url="http://example.com/file.mp3" length="123456789" type="audio/mpeg" />
XMLName xml.Name `xml:"enclosure"`
Url string `xml:"url,attr"`
Length string `xml:"length,attr"`
Type string `xml:"type,attr"`
}
type RssGuid struct {
//RSS 2.0 <guid isPermaLink="true">http://inessential.com/2002/09/01.php#a2</guid>
XMLName xml.Name `xml:"guid"`
Id string `xml:",chardata"`
IsPermaLink string `xml:"isPermaLink,attr,omitempty"` // "true", "false", or an empty string
}
type Rss struct {
*Feed
}
// create a new RssItem with a generic Item struct's data
func newRssItem(i *Item) *RssItem {
item := &RssItem{
Title: i.Title,
Description: i.Description,
PubDate: anyTimeFormat(time.RFC1123Z, i.Created, i.Updated),
}
if i.Id != "" {
item.Guid = &RssGuid{Id: i.Id, IsPermaLink: i.IsPermaLink}
}
if i.Link != nil {
item.Link = i.Link.Href
}
if len(i.Content) > 0 {
item.Content = &RssContent{Content: i.Content}
}
if i.Source != nil {
item.Source = i.Source.Href
}
// Define a closure
if i.Enclosure != nil && i.Enclosure.Type != "" && i.Enclosure.Length != "" {
item.Enclosure = &RssEnclosure{Url: i.Enclosure.Url, Type: i.Enclosure.Type, Length: i.Enclosure.Length}
}
if i.Author != nil {
item.Author = i.Author.Name
}
return item
}
// create a new RssFeed with a generic Feed struct's data
func (r *Rss) RssFeed() *RssFeed {
pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated)
build := anyTimeFormat(time.RFC1123Z, r.Updated)
author := ""
if r.Author != nil {
author = r.Author.Email
if len(r.Author.Name) > 0 {
author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name)
}
}
var image *RssImage
if r.Image != nil {
image = &RssImage{Url: r.Image.Url, Title: r.Image.Title, Link: r.Image.Link, Width: r.Image.Width, Height: r.Image.Height}
}
var href string
if r.Link != nil {
href = r.Link.Href
}
channel := &RssFeed{
Title: r.Title,
Link: href,
Description: r.Description,
ManagingEditor: author,
PubDate: pub,
LastBuildDate: build,
Copyright: r.Copyright,
Image: image,
}
for _, i := range r.Items {
channel.Items = append(channel.Items, newRssItem(i))
}
return channel
}
// FeedXml returns an XML-Ready object for an Rss object
func (r *Rss) FeedXml() interface{} {
// only generate version 2.0 feeds for now
return r.RssFeed().FeedXml()
}
// FeedXml returns an XML-ready object for an RssFeed object
func (r *RssFeed) FeedXml() interface{} {
return &RssFeedXml{
Version: "2.0",
Channel: r,
ContentNamespace: "http://purl.org/rss/1.0/modules/content/",
}
}

92
vendor/github.com/gorilla/feeds/test.atom generated vendored Normal file
View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:atom="http://www.w3.org/2005/Atom">
<title><![CDATA[Lorem ipsum feed for an interval of 1 minutes]]></title>
<description><![CDATA[This is a constantly updating lorem ipsum feed]]></description>
<link>http://example.com/</link>
<generator>RSS for Node</generator>
<lastBuildDate>Tue, 30 Oct 2018 23:22:37 GMT</lastBuildDate>
<author><![CDATA[John Smith]]></author>
<pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate>
<copyright><![CDATA[Michael Bertolacci, licensed under a Creative Commons Attribution 3.0 Unported License.]]></copyright>
<ttl>60</ttl>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:22:00+00:00]]></title>
<description><![CDATA[Exercitation ut Lorem sint proident.]]></description>
<link>http://example.com/test/1540941720</link>
<guid isPermaLink="true">http://example.com/test/1540941720</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:21:00+00:00]]></title>
<description><![CDATA[Ea est do quis fugiat exercitation.]]></description>
<link>http://example.com/test/1540941660</link>
<guid isPermaLink="true">http://example.com/test/1540941660</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:21:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:20:00+00:00]]></title>
<description><![CDATA[Ipsum velit cillum ad laborum sit nulla exercitation consequat sint veniam culpa veniam voluptate incididunt.]]></description>
<link>http://example.com/test/1540941600</link>
<guid isPermaLink="true">http://example.com/test/1540941600</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:20:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:19:00+00:00]]></title>
<description><![CDATA[Ullamco pariatur aliqua consequat ea veniam id qui incididunt laborum.]]></description>
<link>http://example.com/test/1540941540</link>
<guid isPermaLink="true">http://example.com/test/1540941540</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:19:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:18:00+00:00]]></title>
<description><![CDATA[Velit proident aliquip aliquip anim mollit voluptate laboris voluptate et occaecat occaecat laboris ea nulla.]]></description>
<link>http://example.com/test/1540941480</link>
<guid isPermaLink="true">http://example.com/test/1540941480</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:18:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:17:00+00:00]]></title>
<description><![CDATA[Do in quis mollit consequat id in minim laborum sint exercitation laborum elit officia.]]></description>
<link>http://example.com/test/1540941420</link>
<guid isPermaLink="true">http://example.com/test/1540941420</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:17:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:16:00+00:00]]></title>
<description><![CDATA[Irure id sint ullamco Lorem magna consectetur officia adipisicing duis incididunt.]]></description>
<link>http://example.com/test/1540941360</link>
<guid isPermaLink="true">http://example.com/test/1540941360</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:16:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:15:00+00:00]]></title>
<description><![CDATA[Sunt anim excepteur esse nisi commodo culpa laborum exercitation ad anim ex elit.]]></description>
<link>http://example.com/test/1540941300</link>
<guid isPermaLink="true">http://example.com/test/1540941300</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:15:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:14:00+00:00]]></title>
<description><![CDATA[Excepteur aliquip fugiat ex labore nisi.]]></description>
<link>http://example.com/test/1540941240</link>
<guid isPermaLink="true">http://example.com/test/1540941240</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:14:00 GMT</pubDate>
</entry>
<entry>
<title><![CDATA[Lorem ipsum 2018-10-30T23:13:00+00:00]]></title>
<description><![CDATA[Id proident adipisicing proident pariatur aute pariatur pariatur dolor dolor in voluptate dolor.]]></description>
<link>http://example.com/test/1540941180</link>
<guid isPermaLink="true">http://example.com/test/1540941180</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:13:00 GMT</pubDate>
</entry>
</feed>

96
vendor/github.com/gorilla/feeds/test.rss generated vendored Normal file
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title><![CDATA[Lorem ipsum feed for an interval of 1 minutes]]></title>
<description><![CDATA[This is a constantly updating lorem ipsum feed]]></description>
<link>http://example.com/</link>
<generator>RSS for Node</generator>
<lastBuildDate>Tue, 30 Oct 2018 23:22:37 GMT</lastBuildDate>
<author><![CDATA[John Smith]]></author>
<pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate>
<copyright><![CDATA[Michael Bertolacci, licensed under a Creative Commons Attribution 3.0 Unported License.]]></copyright>
<ttl>60</ttl>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:22:00+00:00]]></title>
<description><![CDATA[Exercitation ut Lorem sint proident.]]></description>
<link>http://example.com/test/1540941720</link>
<guid isPermaLink="true">http://example.com/test/1540941720</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:21:00+00:00]]></title>
<description><![CDATA[Ea est do quis fugiat exercitation.]]></description>
<link>http://example.com/test/1540941660</link>
<guid isPermaLink="true">http://example.com/test/1540941660</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:21:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:20:00+00:00]]></title>
<description><![CDATA[Ipsum velit cillum ad laborum sit nulla exercitation consequat sint veniam culpa veniam voluptate incididunt.]]></description>
<link>http://example.com/test/1540941600</link>
<guid isPermaLink="true">http://example.com/test/1540941600</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:20:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:19:00+00:00]]></title>
<description><![CDATA[Ullamco pariatur aliqua consequat ea veniam id qui incididunt laborum.]]></description>
<link>http://example.com/test/1540941540</link>
<guid isPermaLink="true">http://example.com/test/1540941540</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:19:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:18:00+00:00]]></title>
<description><![CDATA[Velit proident aliquip aliquip anim mollit voluptate laboris voluptate et occaecat occaecat laboris ea nulla.]]></description>
<link>http://example.com/test/1540941480</link>
<guid isPermaLink="true">http://example.com/test/1540941480</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:18:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:17:00+00:00]]></title>
<description><![CDATA[Do in quis mollit consequat id in minim laborum sint exercitation laborum elit officia.]]></description>
<link>http://example.com/test/1540941420</link>
<guid isPermaLink="true">http://example.com/test/1540941420</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:17:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:16:00+00:00]]></title>
<description><![CDATA[Irure id sint ullamco Lorem magna consectetur officia adipisicing duis incididunt.]]></description>
<link>http://example.com/test/1540941360</link>
<guid isPermaLink="true">http://example.com/test/1540941360</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:16:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:15:00+00:00]]></title>
<description><![CDATA[Sunt anim excepteur esse nisi commodo culpa laborum exercitation ad anim ex elit.]]></description>
<link>http://example.com/test/1540941300</link>
<guid isPermaLink="true">http://example.com/test/1540941300</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:15:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:14:00+00:00]]></title>
<description><![CDATA[Excepteur aliquip fugiat ex labore nisi.]]></description>
<link>http://example.com/test/1540941240</link>
<guid isPermaLink="true">http://example.com/test/1540941240</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:14:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[Lorem ipsum 2018-10-30T23:13:00+00:00]]></title>
<description><![CDATA[Id proident adipisicing proident pariatur aute pariatur pariatur dolor dolor in voluptate dolor.]]></description>
<link>http://example.com/test/1540941180</link>
<guid isPermaLink="true">http://example.com/test/1540941180</guid>
<dc:creator><![CDATA[John Smith]]></dc:creator>
<pubDate>Tue, 30 Oct 2018 23:13:00 GMT</pubDate>
</item>
</channel>
</rss>

20
vendor/github.com/gorilla/feeds/to-implement.md generated vendored Normal file
View File

@ -0,0 +1,20 @@
[Full iTunes list](https://help.apple.com/itc/podcasts_connect/#/itcb54353390)
[Example of ideal iTunes RSS feed](https://help.apple.com/itc/podcasts_connect/#/itcbaf351599)
```
<itunes:author>
<itunes:block>
<itunes:catergory>
<itunes:image>
<itunes:duration>
<itunes:explicit>
<itunes:isClosedCaptioned>
<itunes:order>
<itunes:complete>
<itunes:new-feed-url>
<itunes:owner>
<itunes:subtitle>
<itunes:summary>
<language>
```

27
vendor/github.com/gorilla/feeds/uuid.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package feeds
// relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go
import (
"crypto/rand"
"fmt"
)
type UUID [16]byte
// create a new uuid v4
func NewUUID() *UUID {
u := &UUID{}
_, err := rand.Read(u[:16])
if err != nil {
panic(err)
}
u[8] = (u[8] | 0x80) & 0xBf
u[6] = (u[6] | 0x40) & 0x4f
return u
}
func (u *UUID) String() string {
return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
}

3
vendor/modules.txt vendored
View File

@ -4,3 +4,6 @@ github.com/dhowden/tag
# github.com/google/uuid v1.6.0 # github.com/google/uuid v1.6.0
## explicit ## explicit
github.com/google/uuid github.com/google/uuid
# github.com/gorilla/feeds v1.2.0
## explicit; go 1.20
github.com/gorilla/feeds