Skip to content

Commit

Permalink
Atom feed support (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
kgaughan authored Jun 25, 2023
1 parent 8efe547 commit 803e9b6
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 0 deletions.
51 changes: 51 additions & 0 deletions cmd/mercury/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/kgaughan/mercury/internal"
"github.com/kgaughan/mercury/internal/atom"
"github.com/kgaughan/mercury/internal/feed"
"github.com/kgaughan/mercury/internal/flags"
"github.com/kgaughan/mercury/internal/manifest"
Expand Down Expand Up @@ -83,6 +84,10 @@ func main() {
log.Fatal(err)
}

if err := writeFeed(entries, config); err != nil {
log.Fatal(err)
}

if err := writeOPML(manifest, path.Join(config.Output, "opml.xml")); err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -166,6 +171,52 @@ func writePages(entries []*feed.Entry, feeds []*gofeed.Feed, config internal.Con
return nil
}

func writeFeed(entries []*feed.Entry, config internal.Config) error {
feed := atom.Feed{
Title: config.Name,
ID: config.FeedID,
Updated: atom.Time(*entries[0].Updated()),
Links: []atom.Link{{
Rel: "self",
Href: config.URL + "feed.atom",
}},
}

for _, entry := range entries {
summary := &atom.Text{
Type: "html",
Body: string(entry.Summary()),
}
if summary.Body == "" {
summary = nil
}
atomEntry := &atom.Entry{
Title: entry.Title(),
ID: entry.ID(),
Links: []atom.Link{{
Rel: "alternate",
Href: string(entry.Link()),
}},
Published: atom.Time(*entry.Published()),
Updated: atom.Time(*entry.Updated()),
Author: &atom.Person{
Name: entry.Author(),
},
Summary: summary,
Content: &atom.Text{
Type: "html",
Body: string(entry.Content()),
},
}
feed.Entries = append(feed.Entries, atomEntry)
}

if err := utils.MarshalToFile(path.Join(config.Output, "atom.xml"), feed); err != nil {
return fmt.Errorf("could not create Atom feed: %w", err)
}
return nil
}

func writeOPML(manifest *manifest.Manifest, path string) error {
opml := opml.New(manifest.Len())
for url, item := range *manifest {
Expand Down
3 changes: 3 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The top-level configuration fields are:
| url | string | The base URL of your planet | "" |
| owner | string | Your name | "" |
| email | string | Your email | "" |
| feed_id | string | Unique ID to use for the Atom feed | "" |
| cache | string | The path, relative to _mercury.toml_ of the feed cache | "./cache" |
| timeout | duration | How long to wait when fetching a feed | - |
| theme | string | The path, relative to _mercury.toml_ of the theme to use | "./theme" |
Expand All @@ -17,6 +18,8 @@ The top-level configuration fields are:

A _duration_ is a sequence of numbers followed by a unit, with 's' being 'second', 'm' being 'minute', and 'h' being 'hour'. Thus '5m30' would mean five minutes and thirty seconds.

The feed ID is a URI identifying the feed. I would recommend using a [tag URI](https://en.wikipedia.org/wiki/Tag_URI_scheme), or a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) [URN](https://en.wikipedia.org/wiki/Uniform_Resource_Name). In the latter case, use a UUID generator such as `uuidgen` to generate a UUID, prefix it with `urn:uuid:`, and use the result as the value of `feed_id`.

Each feed is introduced with `[[feed]]`, and can contain the following fields:

| Name | Type | Description |
Expand Down
54 changes: 54 additions & 0 deletions internal/atom/atom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package atom

import (
"encoding/xml"
"time"
)

type Feed struct {
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
Title string `xml:"title"`
ID string `xml:"id"`
Links []Link `xml:"link"`
Updated TimeStr `xml:"updated"`
Author *Person `xml:"author"`
Entries []*Entry `xml:"entry"`
}

type Entry struct {
Title string `xml:"title"`
ID string `xml:"id"`
Links []Link `xml:"link"`
Published TimeStr `xml:"published"`
Updated TimeStr `xml:"updated"`
Author *Person `xml:"author"`
Summary *Text `xml:"summary,omitempty"`
Content *Text `xml:"content,omitempty"`
}

type Link struct {
Rel string `xml:"rel,attr,omitempty"`
Href string `xml:"href,attr"`
Type string `xml:"type,attr,omitempty"`
HrefLang string `xml:"hreflang,attr,omitempty"`
Title string `xml:"title,attr,omitempty"`
Length uint `xml:"length,attr,omitempty"`
}

type Person struct {
Name string `xml:"name"`
URI string `xml:"uri,omitempty"`
Email string `xml:"email,omitempty"`
InnerXML string `xml:",innerxml"`
}

type Text struct {
Type string `xml:"type,attr"`
Body string `xml:",chardata"`
}

type TimeStr string

func Time(t time.Time) TimeStr {
return TimeStr(t.Format("2006-01-02T15:04:05-07:00"))
}
1 change: 1 addition & 0 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Config struct {
URL string `toml:"url"`
Owner string `toml:"owner"`
Email string `toml:"email"`
FeedID string `toml:"feed_id"`
Cache string `toml:"cache"`
Timeout utils.Duration `toml:"timeout"`
Theme string `toml:"theme"`
Expand Down
4 changes: 4 additions & 0 deletions internal/feed/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,7 @@ func (e Entry) Updated() *time.Time {
func (e Entry) Categories() []string {
return e.entry.Categories
}

func (e Entry) ID() string {
return e.entry.GUID
}
1 change: 1 addition & 0 deletions mercury.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name = "My Planet!"
url = "http://localhost/"
feed_id = "uri:urn:5c71947a-107d-4a3d-b635-2bac0cbcd8ba"
owner = "Chidi Anagonye"
email = "[email protected]"
cache = "./cache"
Expand Down
1 change: 1 addition & 0 deletions theme/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<meta name="generator" content="{{.Generator}}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" integrity="sha384-9Z9AuAj0Xi0z7WFOSgjjow8EnNY9wPNp925TVLlAyWhvZPsf5Ks23Ex0mxIrWJzJ" crossorigin="anonymous">
<link rel="stylesheet" href="static/style.css" type="text/css">
<link rel="alternate" type="application/atom+xml" href="atom.xml" title="Atom feed">
<link rel="outline" type="text/x-opml" href="opml.xml" title="OPML">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
Expand Down

0 comments on commit 803e9b6

Please sign in to comment.