| @@ -4,6 +4,7 @@ import ( |
| 4 | "encoding/json" |
4 | "encoding/json" |
| 5 | "strconv" |
5 | "strconv" |
| 6 | "strings" |
6 | "strings" |
| |
7 | "time" |
| 7 | |
8 | |
| 8 | "github.com/krazywarez/devianter" |
9 | "github.com/krazywarez/devianter" |
| 9 | "golang.org/x/net/html" |
10 | "golang.org/x/net/html" |
| @@ -102,6 +103,7 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con |
| 102 | } |
103 | } |
| 103 | |
104 | |
| 104 | var list, listContent strings.Builder |
105 | var list, listContent strings.Builder |
| |
106 | var newest time.Time // the latest entry, for the feed's own <updated> |
| 105 | |
107 | |
| 106 | for i, l := 0, len(devs); i < l; i++ { |
108 | for i, l := 0, len(devs); i < l; i++ { |
| 107 | data := &devs[i] |
109 | data := &devs[i] |
| @@ -115,21 +117,30 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con |
| 115 | if preview, fullview := esc(ParseMedia(s.Host, data.Media, 320)), esc(ParseMedia(s.Host, data.Media)); true { |
117 | if preview, fullview := esc(ParseMedia(s.Host, data.Media, 320)), esc(ParseMedia(s.Host, data.Media)); true { |
| 116 | if allowAtom && s.Atom { |
118 | if allowAtom && s.Atom { |
| 117 | s.Writer.Header().Add("Content-Type", "application/atom+xml") |
119 | s.Writer.Header().Add("Content-Type", "application/atom+xml") |
| 118 | id := strconv.Itoa(data.ID) |
120 | // The entry id is the post's URL here: Atom wants an IRI, and a |
| |
121 | // bare number is not one. Timestamps are RFC 3339, the only form |
| |
122 | // the format allows. |
| |
123 | entryURL := esc(URLBuilder(s.Host, "post", data.Author.Username, "atom-"+strconv.Itoa(data.ID))) |
| |
124 | published := data.PublishedTime.UTC() |
| |
125 | if published.After(newest) { |
| |
126 | newest = published |
| |
127 | } |
| 119 | listContent.WriteString(`<entry><author><name>`) |
128 | listContent.WriteString(`<entry><author><name>`) |
| 120 | listContent.WriteString(author) |
129 | listContent.WriteString(author) |
| 121 | listContent.WriteString(`</name></author><title>`) |
130 | listContent.WriteString(`</name></author><title>`) |
| 122 | listContent.WriteString(title) |
131 | listContent.WriteString(title) |
| 123 | listContent.WriteString(`</title><link rel="alternate" type="text/html" href="`) |
132 | listContent.WriteString(`</title><link rel="alternate" type="text/html" href="`) |
| 124 | listContent.WriteString(esc(URLBuilder(s.Host, "post", data.Author.Username, "atom-"+id))) |
133 | listContent.WriteString(entryURL) |
| 125 | listContent.WriteString(`"/><id>`) |
134 | listContent.WriteString(`"/><id>`) |
| 126 | listContent.WriteString(id) |
135 | listContent.WriteString(entryURL) |
| 127 | listContent.WriteString(`</id><published>`) |
136 | listContent.WriteString(`</id><published>`) |
| 128 | listContent.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700")) |
137 | listContent.WriteString(published.Format(time.RFC3339)) |
| 129 | listContent.WriteString(`</published>`) |
138 | listContent.WriteString(`</published><updated>`) |
| |
139 | listContent.WriteString(published.Format(time.RFC3339)) |
| |
140 | listContent.WriteString(`</updated>`) |
| 130 | listContent.WriteString(`<media:group><media:title>`) |
141 | listContent.WriteString(`<media:group><media:title>`) |
| 131 | listContent.WriteString(title) |
142 | listContent.WriteString(title) |
| 132 | listContent.WriteString(`</media:title><media:thumbinal url="`) |
143 | listContent.WriteString(`</media:title><media:thumbnail url="`) |
| 133 | listContent.WriteString(preview) |
144 | listContent.WriteString(preview) |
| 134 | listContent.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`) |
145 | listContent.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`) |
| 135 | listContent.WriteString(postURL) |
146 | listContent.WriteString(postURL) |
| @@ -185,6 +196,20 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con |
| 185 | } |
196 | } |
| 186 | list.WriteString(`</title>`) |
197 | list.WriteString(`</title>`) |
| 187 | |
198 | |
| |
199 | // Atom requires a feed id and updated time; readers reject a feed |
| |
200 | // without them. The id is the feed's own URL on this instance. |
| |
201 | feedURL := esc(s.Host + s._pth + "?" + s.Args.Encode()) |
| |
202 | if newest.IsZero() { |
| |
203 | newest = time.Now().UTC() |
| |
204 | } |
| |
205 | list.WriteString(`<id>`) |
| |
206 | list.WriteString(feedURL) |
| |
207 | list.WriteString(`</id><updated>`) |
| |
208 | list.WriteString(newest.Format(time.RFC3339)) |
| |
209 | list.WriteString(`</updated><link rel="self" type="application/atom+xml" href="`) |
| |
210 | list.WriteString(feedURL) |
| |
211 | list.WriteString(`"/>`) |
| |
212 | |
| 188 | list.WriteString(`<link rel="alternate" href="`) |
213 | list.WriteString(`<link rel="alternate" href="`) |
| 189 | list.WriteString(esc(s.Host)) |
214 | list.WriteString(esc(s.Host)) |
| 190 | list.WriteString(`"/>`) |
215 | list.WriteString(`"/>`) |