forked from SlyMarbo/rss
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrss.go
168 lines (139 loc) · 3.41 KB
/
rss.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package rss
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
)
// Parse RSS or Atom data.
func Parse(data []byte) (*Feed, error) {
if strings.Contains(string(data), "<rss") {
return parseRSS2(data, database)
} else if strings.Contains(string(data), "xmlns=\"http://purl.org/rss/1.0/\"") {
return parseRSS1(data, database)
} else {
return parseAtom(data, database)
}
panic("Unreachable.")
}
// CacheParsedItemIDs enables or disable Item.ID caching when parsing feeds.
// Returns whether Item.ID were cached prior to function call.
func CacheParsedItemIDs(flag bool) (didCache bool) {
didCache = !disabled
disabled = !flag
return
}
type FetchFunc func() (resp *http.Response, err error)
// Fetch downloads and parses the RSS feed at the given URL
func Fetch(url string) (*Feed, error) {
return FetchByClient(url, http.DefaultClient)
}
func FetchByClient(url string, client *http.Client) (*Feed, error) {
fetchFunc := func() (resp *http.Response, err error) {
return client.Get(url)
}
return FetchByFunc(fetchFunc, url)
}
func FetchByFunc(fetchFunc FetchFunc, url string) (*Feed, error) {
resp, err := fetchFunc()
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
out, err := Parse(body)
if err != nil {
return nil, err
}
if out.Link == "" {
out.Link = url
}
out.UpdateURL = url
return out, nil
}
// Feed is the top-level structure.
type Feed struct {
Nickname string
Title string
Description string
Link string
UpdateURL string
Image *Image
Items []*Item
ItemMap map[string]struct{}
Refresh time.Time
Unread uint32
}
// Update fetches any new items and updates f.
func (f *Feed) Update() error {
// Check that we don't update too often.
if f.Refresh.After(time.Now()) {
return nil
}
if f.UpdateURL == "" {
return errors.New("Error: feed has no URL.")
}
if f.ItemMap == nil {
f.ItemMap = make(map[string]struct{})
for _, item := range f.Items {
if _, ok := f.ItemMap[item.ID]; !ok {
f.ItemMap[item.ID] = struct{}{}
}
}
}
update, err := Fetch(f.UpdateURL)
if err != nil {
return err
}
f.Refresh = update.Refresh
f.Title = update.Title
f.Description = update.Description
for _, item := range update.Items {
if _, ok := f.ItemMap[item.ID]; !ok {
f.Items = append(f.Items, item)
f.ItemMap[item.ID] = struct{}{}
f.Unread++
}
}
return nil
}
func (f *Feed) String() string {
buf := new(bytes.Buffer)
buf.WriteString(fmt.Sprintf("Feed %q\n\t%q\n\t%q\n\t%s\n\tRefresh at %s\n\tUnread: %d\n\tItems:\n",
f.Title, f.Description, f.Link, f.Image, f.Refresh.Format("Mon 2 Jan 2006 15:04:05 MST"), f.Unread))
for _, item := range f.Items {
buf.WriteString(fmt.Sprintf("\t%s\n", item.Format("\t\t")))
}
return buf.String()
}
// Item represents a single story.
type Item struct {
Title string
Content string
Link string
Date time.Time
ID string
Read bool
}
func (i *Item) String() string {
return i.Format("")
}
func (i *Item) Format(s string) string {
return fmt.Sprintf("Item %q\n\t%s%q\n\t%s%s\n\t%s%q\n\t%sRead: %v\n\t%s%q", i.Title, s, i.Link, s,
i.Date.Format("Mon 2 Jan 2006 15:04:05 MST"), s, i.ID, s, i.Read, s, i.Content)
}
type Image struct {
Title string
Url string
Height uint32
Width uint32
}
func (i *Image) String() string {
return fmt.Sprintf("Image %q", i.Title)
}