bring back yahoo pipes!
1package outputs
2
3import (
4 "context"
5 "encoding/xml"
6 "fmt"
7 "time"
8
9 "github.com/kierank/pipes/nodes"
10)
11
12type RSSOutputNode struct{}
13
14func (n *RSSOutputNode) Type() string { return "rss-output" }
15func (n *RSSOutputNode) Label() string { return "RSS Output" }
16func (n *RSSOutputNode) Description() string { return "Output data as RSS feed" }
17func (n *RSSOutputNode) Category() string { return "output" }
18func (n *RSSOutputNode) Inputs() int { return 1 }
19func (n *RSSOutputNode) Outputs() int { return 0 }
20
21func (n *RSSOutputNode) Execute(ctx context.Context, config map[string]interface{}, inputs [][]interface{}, execCtx *nodes.Context) ([]interface{}, error) {
22 if len(inputs) == 0 || len(inputs[0]) == 0 {
23 execCtx.Log("rss-output", "info", "No input data")
24 return nil, nil
25 }
26
27 data := inputs[0]
28
29 // Get feed metadata from config
30 feedTitle := getStringConfig(config, "title", "Pipes Feed")
31 feedDescription := getStringConfig(config, "description", "Feed generated by Pipes")
32 feedLink := getStringConfig(config, "link", "http://localhost:3001")
33
34 // Build RSS feed
35 type RSSItem struct {
36 Title string `xml:"title"`
37 Description string `xml:"description"`
38 Link string `xml:"link"`
39 PubDate string `xml:"pubDate,omitempty"`
40 GUID string `xml:"guid,omitempty"`
41 Author string `xml:"author,omitempty"`
42 Categories []string `xml:"category,omitempty"`
43 }
44
45 type RSSChannel struct {
46 Title string `xml:"title"`
47 Description string `xml:"description"`
48 Link string `xml:"link"`
49 Items []RSSItem `xml:"item"`
50 }
51
52 type RSS struct {
53 XMLName xml.Name `xml:"rss"`
54 Version string `xml:"version,attr"`
55 Channel RSSChannel `xml:"channel"`
56 }
57
58 var items []RSSItem
59 for _, item := range data {
60 itemMap, ok := item.(map[string]interface{})
61 if !ok {
62 continue
63 }
64
65 rssItem := RSSItem{
66 Title: getStringFromMap(itemMap, "title", "Untitled"),
67 Description: getStringFromMap(itemMap, "description", ""),
68 Link: getStringFromMap(itemMap, "link", ""),
69 GUID: getStringFromMap(itemMap, "guid", ""),
70 Author: getStringFromMap(itemMap, "author", ""),
71 }
72
73 // Try to get published date - check "published" first, then fall back to "published_at" timestamp
74 if pubDate, ok := itemMap["published"].(string); ok && pubDate != "" {
75 rssItem.PubDate = pubDate
76 } else if timestamp, ok := itemMap["published_at"].(int64); ok && timestamp > 0 {
77 // Convert Unix timestamp to RFC1123 format for RSS
78 rssItem.PubDate = time.Unix(timestamp, 0).UTC().Format(time.RFC1123Z)
79 }
80
81 // Extract categories if present
82 if categories, ok := itemMap["categories"].([]interface{}); ok {
83 for _, cat := range categories {
84 if catStr, ok := cat.(string); ok {
85 rssItem.Categories = append(rssItem.Categories, catStr)
86 }
87 }
88 }
89
90 items = append(items, rssItem)
91 }
92
93 feed := RSS{
94 Version: "2.0",
95 Channel: RSSChannel{
96 Title: feedTitle,
97 Description: feedDescription,
98 Link: feedLink,
99 Items: items,
100 },
101 }
102
103 xmlData, err := xml.MarshalIndent(feed, "", " ")
104 if err != nil {
105 return nil, fmt.Errorf("marshal RSS: %w", err)
106 }
107
108 rssOutput := fmt.Sprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n%s", string(xmlData))
109
110 // Save output to database for public access
111 if err := execCtx.SaveOutput("rss", rssOutput, "application/rss+xml"); err != nil {
112 execCtx.Log("rss-output", "error", "Failed to save output: "+err.Error())
113 }
114
115 execCtx.Log("rss-output", "info", rssOutput)
116
117 return data, nil
118}
119
120func (n *RSSOutputNode) ValidateConfig(config map[string]interface{}) error {
121 return nil
122}
123
124func (n *RSSOutputNode) GetConfigSchema() *nodes.ConfigSchema {
125 return &nodes.ConfigSchema{
126 Fields: []nodes.ConfigField{
127 {
128 Name: "title",
129 Label: "Feed Title",
130 Type: "text",
131 Required: false,
132 DefaultValue: "Pipes Feed",
133 HelpText: "Title of the RSS feed",
134 },
135 {
136 Name: "description",
137 Label: "Feed Description",
138 Type: "textarea",
139 Required: false,
140 DefaultValue: "Feed generated by Pipes",
141 HelpText: "Description of the RSS feed",
142 },
143 {
144 Name: "link",
145 Label: "Feed Link",
146 Type: "url",
147 Required: false,
148 DefaultValue: "http://localhost:3001",
149 HelpText: "URL of the feed",
150 },
151 },
152 }
153}
154
155func getStringConfig(config map[string]interface{}, key, defaultValue string) string {
156 if val, ok := config[key].(string); ok && val != "" {
157 return val
158 }
159 return defaultValue
160}
161
162func getStringFromMap(m map[string]interface{}, key, defaultValue string) string {
163 if val, ok := m[key].(string); ok {
164 return val
165 }
166 return defaultValue
167}