feed_converter.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. package handlers
  2. import (
  3. "encoding/xml"
  4. "time"
  5. "github.com/mk6i/open-oscar-server/state"
  6. )
  7. // FeedConverter handles conversion of feed data to various output formats.
  8. type FeedConverter struct{}
  9. // NewFeedConverter creates a new feed converter.
  10. func NewFeedConverter() *FeedConverter {
  11. return &FeedConverter{}
  12. }
  13. // FeedResponse wraps feed data with conversion methods.
  14. type FeedResponse struct {
  15. Feed state.BuddyFeed `json:"feed"`
  16. Items []state.BuddyFeedItem `json:"items"`
  17. }
  18. // ToRSS converts the feed response to RSS format.
  19. func (fr *FeedResponse) ToRSS() *RSSFeed {
  20. rss := &RSSFeed{
  21. Version: "2.0",
  22. Channel: RSSChannel{
  23. Title: fr.Feed.Title,
  24. Link: fr.Feed.Link,
  25. Description: fr.Feed.Description,
  26. Language: "en-US",
  27. PubDate: fr.Feed.PublishedAt.Format(time.RFC1123Z),
  28. Items: make([]RSSItem, 0, len(fr.Items)),
  29. },
  30. }
  31. for _, item := range fr.Items {
  32. rssItem := RSSItem{
  33. Title: item.Title,
  34. Link: item.Link,
  35. Description: item.Description,
  36. Author: item.Author,
  37. Categories: item.Categories,
  38. GUID: item.GUID,
  39. PubDate: item.PublishedAt.Format(time.RFC1123Z),
  40. }
  41. rss.Channel.Items = append(rss.Channel.Items, rssItem)
  42. }
  43. return rss
  44. }
  45. // ToAtom converts the feed response to Atom format.
  46. func (fr *FeedResponse) ToAtom() *AtomFeed {
  47. atom := &AtomFeed{
  48. Title: fr.Feed.Title,
  49. Link: AtomLink{Href: fr.Feed.Link, Rel: "alternate"},
  50. Updated: fr.Feed.UpdatedAt.Format(time.RFC3339),
  51. ID: fr.Feed.Link,
  52. Author: AtomAuthor{Name: fr.Feed.ScreenName},
  53. Entries: make([]AtomEntry, 0, len(fr.Items)),
  54. }
  55. for _, item := range fr.Items {
  56. entry := AtomEntry{
  57. Title: item.Title,
  58. Link: AtomLink{Href: item.Link},
  59. ID: item.GUID,
  60. Updated: item.PublishedAt.Format(time.RFC3339),
  61. Published: item.PublishedAt.Format(time.RFC3339),
  62. Author: AtomAuthor{Name: item.Author},
  63. Summary: item.Description,
  64. Content: AtomContent{Type: "html", Text: item.Description},
  65. }
  66. atom.Entries = append(atom.Entries, entry)
  67. }
  68. return atom
  69. }
  70. // ToJSON converts the feed response to JSON format.
  71. func (fr *FeedResponse) ToJSON() map[string]interface{} {
  72. jsonItems := make([]map[string]interface{}, 0, len(fr.Items))
  73. for _, item := range fr.Items {
  74. jsonItem := map[string]interface{}{
  75. "id": item.GUID,
  76. "title": item.Title,
  77. "description": item.Description,
  78. "link": item.Link,
  79. "author": item.Author,
  80. "categories": item.Categories,
  81. "published": item.PublishedAt.Unix(),
  82. }
  83. jsonItems = append(jsonItems, jsonItem)
  84. }
  85. return map[string]interface{}{
  86. "title": fr.Feed.Title,
  87. "description": fr.Feed.Description,
  88. "link": fr.Feed.Link,
  89. "updated": fr.Feed.UpdatedAt.Unix(),
  90. "items": jsonItems,
  91. }
  92. }
  93. // RSS/Atom feed structures for XML output
  94. type RSSFeed struct {
  95. XMLName xml.Name `xml:"rss"`
  96. Version string `xml:"version,attr"`
  97. Channel RSSChannel `xml:"channel"`
  98. }
  99. type RSSChannel struct {
  100. Title string `xml:"title"`
  101. Link string `xml:"link"`
  102. Description string `xml:"description"`
  103. Language string `xml:"language,omitempty"`
  104. PubDate string `xml:"pubDate,omitempty"`
  105. Items []RSSItem `xml:"item"`
  106. }
  107. type RSSItem struct {
  108. Title string `xml:"title"`
  109. Link string `xml:"link"`
  110. Description string `xml:"description"`
  111. Author string `xml:"author,omitempty"`
  112. Categories []string `xml:"category,omitempty"`
  113. GUID string `xml:"guid,omitempty"`
  114. PubDate string `xml:"pubDate"`
  115. }
  116. type AtomFeed struct {
  117. XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
  118. Title string `xml:"title"`
  119. Link AtomLink `xml:"link"`
  120. Updated string `xml:"updated"`
  121. Author AtomAuthor `xml:"author,omitempty"`
  122. ID string `xml:"id"`
  123. Entries []AtomEntry `xml:"entry"`
  124. }
  125. type AtomLink struct {
  126. Href string `xml:"href,attr"`
  127. Rel string `xml:"rel,attr,omitempty"`
  128. }
  129. type AtomAuthor struct {
  130. Name string `xml:"name"`
  131. }
  132. type AtomEntry struct {
  133. Title string `xml:"title"`
  134. Link AtomLink `xml:"link"`
  135. ID string `xml:"id"`
  136. Updated string `xml:"updated"`
  137. Published string `xml:"published,omitempty"`
  138. Author AtomAuthor `xml:"author,omitempty"`
  139. Summary string `xml:"summary,omitempty"`
  140. Content AtomContent `xml:"content,omitempty"`
  141. }
  142. type AtomContent struct {
  143. Type string `xml:"type,attr"`
  144. Text string `xml:",chardata"`
  145. }
  146. // GenerateEmptyFeed creates an empty feed for users without configured feeds.
  147. func GenerateEmptyFeed(screenName string) *FeedResponse {
  148. feed := state.BuddyFeed{
  149. ScreenName: screenName,
  150. Title: screenName + "'s Feed",
  151. Description: "No updates from " + screenName,
  152. Link: "/buddyfeed/getUser?u=" + screenName,
  153. PublishedAt: time.Now(),
  154. UpdatedAt: time.Now(),
  155. }
  156. return &FeedResponse{
  157. Feed: feed,
  158. Items: []state.BuddyFeedItem{},
  159. }
  160. }
  161. // BuildFeedData creates feed data map from request parameters.
  162. func BuildFeedData(params map[string]string) map[string]interface{} {
  163. feedData := make(map[string]interface{})
  164. // Required fields
  165. if title, ok := params["itemTitle"]; ok {
  166. feedData["title"] = title
  167. }
  168. if desc, ok := params["itemDesc"]; ok {
  169. feedData["description"] = desc
  170. }
  171. if link, ok := params["itemLink"]; ok {
  172. feedData["link"] = link
  173. }
  174. if guid, ok := params["itemGuid"]; ok {
  175. feedData["guid"] = guid
  176. }
  177. // Feed metadata
  178. if feedTitle, ok := params["feedTitle"]; ok {
  179. feedData["feedTitle"] = feedTitle
  180. }
  181. if feedLink, ok := params["feedLink"]; ok {
  182. feedData["feedLink"] = feedLink
  183. }
  184. if feedDesc, ok := params["feedDesc"]; ok {
  185. feedData["feedDesc"] = feedDesc
  186. }
  187. // Optional fields
  188. if publisher, ok := params["feedPublisher"]; ok && publisher != "" {
  189. feedData["publisher"] = publisher
  190. }
  191. if pubDate, ok := params["itemPubDate"]; ok && pubDate != "" {
  192. feedData["pubDate"] = pubDate
  193. }
  194. if category, ok := params["itemCategory"]; ok && category != "" {
  195. feedData["categories"] = []string{category}
  196. }
  197. // Default type if not specified
  198. if _, ok := feedData["type"]; !ok {
  199. feedData["type"] = "status"
  200. }
  201. return feedData
  202. }