182 lines
4.7 KiB
Go
182 lines
4.7 KiB
Go
package rss
|
|
|
|
import (
|
|
"github.com/yaoapp/gou/process"
|
|
"github.com/yaoapp/kun/exception"
|
|
)
|
|
|
|
func init() {
|
|
process.RegisterGroup("rss", map[string]process.Handler{
|
|
"parse": ProcessParse,
|
|
"validate": ProcessValidate,
|
|
"discover": ProcessDiscover,
|
|
"build": ProcessBuild,
|
|
"fetch": ProcessFetch,
|
|
})
|
|
}
|
|
|
|
// ProcessParse handles the rss.Parse process.
|
|
// Parses an RSS 2.0 or Atom 1.0 XML string into a unified Feed object.
|
|
// Auto-detects format (RSS 2.0, Atom 1.0) and extracts Podcast/iTunes metadata if present.
|
|
//
|
|
// Args:
|
|
// - data string - The feed XML string to parse
|
|
//
|
|
// Returns: Feed object (map representation)
|
|
//
|
|
// Usage:
|
|
//
|
|
// var feed = Process("rss.Parse", xmlString)
|
|
// // feed.format → "rss2.0" or "atom1.0"
|
|
// // feed.title → "My Blog"
|
|
// // feed.items → [{title: "Post 1", ...}, ...]
|
|
// // feed.podcast → {author: "...", ...} (nil for non-podcast feeds)
|
|
func ProcessParse(p *process.Process) interface{} {
|
|
p.ValidateArgNums(1)
|
|
data := p.ArgsString(0)
|
|
|
|
feed, err := Parse(data)
|
|
if err != nil {
|
|
exception.New("rss.parse error: %s", 500, err).Throw()
|
|
}
|
|
return feed
|
|
}
|
|
|
|
// ProcessValidate handles the rss.Validate process.
|
|
// Checks whether the input string is a valid RSS 2.0 or Atom 1.0 feed.
|
|
//
|
|
// Args:
|
|
// - data string - The feed XML string to validate
|
|
//
|
|
// Returns:
|
|
// - true (bool) if the feed is valid
|
|
// - error description string if invalid (AI-friendly message)
|
|
//
|
|
// Usage:
|
|
//
|
|
// var result = Process("rss.Validate", xmlString)
|
|
// if (result !== true) {
|
|
// console.log("Invalid feed: " + result)
|
|
// }
|
|
func ProcessValidate(p *process.Process) interface{} {
|
|
p.ValidateArgNums(1)
|
|
data := p.ArgsString(0)
|
|
|
|
err := Validate(data)
|
|
if err != nil {
|
|
return err.Error()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ProcessBuild handles the rss.Build process.
|
|
// Generates an XML feed document from a Feed object.
|
|
//
|
|
// Args:
|
|
// - feed map - Feed object (same structure as rss.Parse output)
|
|
// - format string (optional) - Output format: "rss" (default) or "atom"
|
|
//
|
|
// Returns: XML string
|
|
//
|
|
// Usage:
|
|
//
|
|
// // Build RSS 2.0 (default)
|
|
// var xml = Process("rss.Build", feedObj)
|
|
//
|
|
// // Build Atom 1.0
|
|
// var xml = Process("rss.Build", feedObj, "atom")
|
|
//
|
|
// // Round-trip: parse then rebuild
|
|
// var feed = Process("rss.Parse", originalXML)
|
|
// var rebuilt = Process("rss.Build", feed, "rss")
|
|
func ProcessBuild(p *process.Process) interface{} {
|
|
p.ValidateArgNums(1)
|
|
|
|
feedData := p.Args[0]
|
|
feed, err := mapToFeed(feedData)
|
|
if err != nil {
|
|
exception.New("rss.build error: %s", 500, err).Throw()
|
|
}
|
|
|
|
format := ""
|
|
if len(p.Args) > 1 {
|
|
format = p.ArgsString(1)
|
|
}
|
|
|
|
result, err := Build(feed, format)
|
|
if err != nil {
|
|
exception.New("rss.build error: %s", 500, err).Throw()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ProcessDiscover handles the rss.Discover process.
|
|
// Extracts feed URLs from HTML, Markdown, or plain text content using regex-based detection.
|
|
// Does not perform any network requests.
|
|
//
|
|
// Args:
|
|
// - text string - The text content to scan for feed URLs
|
|
//
|
|
// Returns: array of FeedLink objects [{url, title, type}, ...]
|
|
//
|
|
// Usage:
|
|
//
|
|
// var links = Process("rss.Discover", htmlString)
|
|
// // links → [{url: "https://example.com/feed.xml", title: "My Blog", type: "rss"}, ...]
|
|
func ProcessDiscover(p *process.Process) interface{} {
|
|
p.ValidateArgNums(1)
|
|
text := p.ArgsString(0)
|
|
|
|
links := Discover(text)
|
|
if links == nil {
|
|
return []FeedLink{}
|
|
}
|
|
return links
|
|
}
|
|
|
|
// ProcessFetch handles the rss.Fetch process.
|
|
// Fetches a remote RSS/Atom feed by URL and returns the parsed Feed along with
|
|
// HTTP metadata (ETag, Last-Modified) for conditional polling.
|
|
// Supports gzip decompression and conditional requests (If-None-Match / If-Modified-Since).
|
|
//
|
|
// Args:
|
|
// - url string - The feed URL to fetch
|
|
// - options map (optional) - {user_agent, timeout, etag, last_modified}
|
|
//
|
|
// Returns: FetchResult {feed, status_code, etag, last_modified, not_modified}
|
|
//
|
|
// Usage:
|
|
//
|
|
// // First fetch
|
|
// var result = Process("rss.Fetch", "https://example.com/feed.xml")
|
|
// // result.feed.title → "My Blog"
|
|
// // result.etag → "abc123"
|
|
// // result.last_modified → "Wed, 01 Jan 2025 00:00:00 GMT"
|
|
//
|
|
// // Subsequent polling with conditional request (saves bandwidth)
|
|
// var result2 = Process("rss.Fetch", "https://example.com/feed.xml", {
|
|
// etag: result.etag,
|
|
// last_modified: result.last_modified
|
|
// })
|
|
// if (result2.not_modified) {
|
|
// // Feed unchanged, skip processing
|
|
// }
|
|
func ProcessFetch(p *process.Process) interface{} {
|
|
p.ValidateArgNums(1)
|
|
url := p.ArgsString(0)
|
|
|
|
var opts *FetchOptions
|
|
if len(p.Args) > 1 {
|
|
o, err := mapToFetchOptions(p.Args[1])
|
|
if err != nil {
|
|
exception.New("rss.fetch error: %s", 500, err).Throw()
|
|
}
|
|
opts = o
|
|
}
|
|
|
|
result, err := Fetch(url, opts)
|
|
if err != nil {
|
|
exception.New("rss.fetch error: %s", 500, err).Throw()
|
|
}
|
|
return result
|
|
}
|