all repos

rss-tools @ 7235086123c8b37c0f4db89fb5821d89d0e29094

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
telegram: turn links into links; return youtube links in youtube official feed format, 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
	Links   []AtomLink  `xml:"link,omitempty"`
28
	Content AtomContent `xml:"content"`
29
}
30
31
type AtomContent struct {
32
	XMLName xml.Name `xml:"content"`
33
	Type    string   `xml:"type,attr,omitempty"`
34
	Value   string   `xml:",chardata"`
35
}
36
37
type AtomLink struct {
38
	Rel  string `xml:"rel,attr,omitempty"`
39
	Type string `xml:"type,attr,omitempty"`
40
	Href string `xml:"href,attr"`
41
}
42
43
type FeedEntry struct {
44
	Title       string
45
	ID          string
46
	Links       []FeedLink
47
	Content     string
48
	ContentType string // "text" or "html", defaults to "text"
49
	Updated     time.Time
50
}
51
52
type FeedLink struct {
53
	Rel  string
54
	Type string
55
	Href string
56
}
57
58
type FeedBuilder struct{ f AtomFeed }
59
60
func NewFeed(title, id string) *FeedBuilder {
61
	return &FeedBuilder{f: AtomFeed{
62
		XMLNS:   "http://www.w3.org/2005/Atom",
63
		Title:   title,
64
		ID:      id,
65
		Updated: time.Now().Format(time.RFC3339),
66
	}}
67
}
68
69
func (f *FeedBuilder) WithSubtitle(subtitle string) *FeedBuilder {
70
	f.f.Subtitle = subtitle
71
	return f
72
}
73
74
func (f *FeedBuilder) WithUpdated(updated time.Time) *FeedBuilder {
75
	if !updated.IsZero() {
76
		f.f.Updated = updated.Format(time.RFC3339)
77
	}
78
	return f
79
}
80
81
func (f *FeedBuilder) Add(entry FeedEntry) *FeedBuilder {
82
	if entry.Updated.IsZero() {
83
		entry.Updated = time.Now()
84
	}
85
	if entry.ID == "" {
86
		hash := sha1.Sum(fmt.Appendf(nil, "%s|%s|%s", entry.Title, entry.Content, entry.Updated.Format(time.RFC3339Nano)))
87
		entry.ID = fmt.Sprintf("urn:sha1:%x", hash)
88
	}
89
90
	contentType := entry.ContentType
91
	if contentType == "" {
92
		contentType = "text"
93
	}
94
95
	links := make([]AtomLink, 0, len(entry.Links))
96
	for _, link := range entry.Links {
97
		if link.Href == "" {
98
			continue
99
		}
100
		links = append(links, AtomLink(link))
101
	}
102
103
	f.f.Entries = append(f.f.Entries, AtomEntry{
104
		Title:   entry.Title,
105
		ID:      entry.ID,
106
		Updated: entry.Updated.Format(time.RFC3339),
107
		Links:   links,
108
		Content: AtomContent{
109
			Type:  contentType,
110
			Value: entry.Content,
111
		},
112
	})
113
114
	feedUpdated, err := time.Parse(time.RFC3339, f.f.Updated)
115
	if err != nil || entry.Updated.After(feedUpdated) {
116
		f.f.Updated = entry.Updated.Format(time.RFC3339)
117
	}
118
	return f
119
}
120
121
func (f *FeedBuilder) SetUpdated(updated time.Time) *FeedBuilder {
122
	if !updated.IsZero() {
123
		f.f.Updated = updated.Format(time.RFC3339)
124
	}
125
	return f
126
}
127
128
func (f *FeedBuilder) WriteTo(w io.Writer) error {
129
	enc := xml.NewEncoder(w)
130
	enc.Indent("", "  ")
131
	return enc.Encode(f.f)
132
}
133
134
func (f *FeedBuilder) Bytes() ([]byte, error) {
135
	var buf bytes.Buffer
136
	if err := f.WriteTo(&buf); err != nil {
137
		return nil, err
138
	}
139
	return buf.Bytes(), nil
140
}
141
142
func (f *FeedBuilder) Render(w http.ResponseWriter) error {
143
	w.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
144
	return f.WriteTo(w)
145
}