all repos

rss-tools @ 97e6bf5d8882b12205658af7ee1de0dd8d42298a

get rss feed from sources that(i need and) dont provide one

rss-tools/app/atom.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
update feed generator, 1 month ago
1
package app
2
3
import (
4
	"bytes"
5
	"crypto/sha1"
6
	"encoding/xml"
7
	"fmt"
8
	"io"
9
	"net/http"
10
	"time"
11
)
12
13
type AtomFeed struct {
14
	XMLName  xml.Name    `xml:"feed"`
15
	XMLNS    string      `xml:"xmlns,attr"`
16
	Title    string      `xml:"title"`
17
	ID       string      `xml:"id"`
18
	Updated  string      `xml:"updated"`
19
	Subtitle string      `xml:"subtitle,omitempty"`
20
	Entries  []AtomEntry `xml:"entry"`
21
}
22
23
type AtomEntry struct {
24
	Title   string      `xml:"title"`
25
	ID      string      `xml:"id"`
26
	Updated string      `xml:"updated"`
27
	Content AtomContent `xml:"content"`
28
}
29
30
type AtomContent struct {
31
	XMLName xml.Name `xml:"content"`
32
	Type    string   `xml:"type,attr,omitempty"`
33
	Value   string   `xml:",chardata"`
34
}
35
36
type FeedEntry struct {
37
	Title       string
38
	ID          string
39
	Content     string
40
	ContentType string // "text" or "html", defaults to "text"
41
	Updated     time.Time
42
}
43
44
type FeedOption func(*AtomFeed)
45
46
func WithFeedSubtitle(subtitle string) FeedOption {
47
	return func(f *AtomFeed) {
48
		f.Subtitle = subtitle
49
	}
50
}
51
52
func WithFeedUpdated(updated time.Time) FeedOption {
53
	return func(f *AtomFeed) {
54
		if !updated.IsZero() {
55
			f.Updated = updated.Format(time.RFC3339)
56
		}
57
	}
58
}
59
60
type FeedBuilder struct {
61
	feed AtomFeed
62
}
63
64
func NewFeed(title, id string, opts ...FeedOption) *FeedBuilder {
65
	builder := &FeedBuilder{feed: AtomFeed{
66
		XMLNS:   "http://www.w3.org/2005/Atom",
67
		Title:   title,
68
		ID:      id,
69
		Updated: time.Now().Format(time.RFC3339),
70
	}}
71
	for _, opt := range opts {
72
		opt(&builder.feed)
73
	}
74
	return builder
75
}
76
77
func (f *FeedBuilder) Add(title, id, content string, date time.Time) *FeedBuilder {
78
	return f.AddEntry(FeedEntry{
79
		Title:   title,
80
		ID:      id,
81
		Content: content,
82
		Updated: date,
83
	})
84
}
85
86
func (f *FeedBuilder) AddText(title, content string, updated time.Time) *FeedBuilder {
87
	return f.AddEntry(FeedEntry{
88
		Title:   title,
89
		Content: content,
90
		Updated: updated,
91
	})
92
}
93
94
func (f *FeedBuilder) AddEntry(entry FeedEntry) *FeedBuilder {
95
	if entry.Updated.IsZero() {
96
		entry.Updated = time.Now()
97
	}
98
	if entry.ID == "" {
99
		hash := sha1.Sum(fmt.Appendf(nil, "%s|%s|%s", entry.Title, entry.Content, entry.Updated.Format(time.RFC3339Nano)))
100
		entry.ID = fmt.Sprintf("urn:sha1:%x", hash)
101
	}
102
103
	f.feed.Entries = append(f.feed.Entries, AtomEntry{
104
		Title:   entry.Title,
105
		ID:      entry.ID,
106
		Updated: entry.Updated.Format(time.RFC3339),
107
		Content: AtomContent{
108
			Type:  contentType,
109
			Value: entry.Content,
110
		},
111
	})
112
113
	feedUpdated, err := time.Parse(time.RFC3339, f.feed.Updated)
114
	if err != nil || entry.Updated.After(feedUpdated) {
115
		f.feed.Updated = entry.Updated.Format(time.RFC3339)
116
	}
117
	return f
118
}
119
120
func (f *FeedBuilder) SetUpdated(updated time.Time) *FeedBuilder {
121
	if !updated.IsZero() {
122
		f.feed.Updated = updated.Format(time.RFC3339)
123
	}
124
	return f
125
}
126
127
func (f *FeedBuilder) WriteTo(w io.Writer) error {
128
	enc := xml.NewEncoder(w)
129
	enc.Indent("", "  ")
130
	return enc.Encode(f.feed)
131
}
132
133
func (f *FeedBuilder) Bytes() ([]byte, error) {
134
	var buf bytes.Buffer
135
	if err := f.WriteTo(&buf); err != nil {
136
		return nil, err
137
	}
138
	return buf.Bytes(), nil
139
}
140
141
func (f *FeedBuilder) Render(w http.ResponseWriter) error {
142
	w.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
143
	return f.WriteTo(w)
144
}