Kinbiko

Go: gofeedme - A simple golang RSS feed parser for Übersicht

February 05, 2018

Some technical knowledge assumed.

This weekend was spent in Brussels, Belgium attending the FOSDEM 18 conferences. I spent a lot of time there at the golang devroom, listenting to talks by some big names in Go, like Francesc Campoy and Sam Boyer, which inspired me to write some Go in my hotelroom. I’m a fan of the Übersicht project, essentially Conky for OSX, and can be configured to look something like:

My desktop using Übersicht

The top left part of the screen displays the output of an RSS feed parser I’ve written, called gofeedme. It’s inspired by, but a complete rewrite of, the RSSs-Widget. You can find the source code here.

It takes the feeds as listed in a config.json file, and outputs the first 3 articles as html links. This is then hooked into Übersicht through a trivial coffeescript file.

The Go code for doing this is quite simple, as the standard library and third party packages make developing a breeze. Essentially there are three steps:

  1. Parse the config.json file.
  2. Request each feed using the mmcdole/gofeed.
  3. Send the html to standard output.

Parsing a json file from the filesystem:

First declare the expected format. Here the `json:` tags represent the name to find it under in the .json file.

type feed struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

type config struct {
	Feeds []feed `json:"feeds"`
}

Second, read the config.json into a byte array:

bytes, err := ioutil.ReadFile(filename)
if err != nil {
	fmt.Println("unable to parse file:", err)
	panic(err)
}

Then use these structs to unmarshal the JSON.

conf := config{}
err = json.Unmarshal(bytes, &conf)
if err != nil {
	fmt.Println("unable to unmarshal file to config", err)
	panic(err)
}
return conf

Request RSS feeds

We can delegate to mmcdole/gofeed for actually parsing the RSS feed, and give us a nice struct with the details we may want.

for _, configFeed := range config.Feeds {
	parsedFeed, err := gofeed.NewParser().ParseURL(configFeed.URL)
	if err != nil {
		fmt.Println("unable to parse feed, ignoring "+configFeed.Name, err)
		continue
	}

  //...
}

Print a HTML representation the headlines as links.

Simply print html strings using the fmt package all gophers will know well. Prints 3 links per feed:

func printLinks(rssFeed *gofeed.Feed) {
	//...
	fmt.Printf("<h4>%s</h4>\n", name)
	fmt.Println("<ul>")
	for _, story := range rssFeed.Items[:3] {
		fmt.Printf("<li>")
		fmt.Printf("<a href='%s'>", story.Link)
		fmt.Printf(story.Title)
		fmt.Printf("</a>")
		fmt.Println("</li>")
	}
	fmt.Println("</ul>")
}

Nicer error handling

I’ve also added an offline check (one of the reasons I wanted to rewrite RSSs-Widget - this looked really ugly when offline):

func offline() bool {
	//Chances are if Google's down, the internet is down.
	_, err := http.Get("http://google.com/")
	return err != nil
}

Then in the beginning of main.go:

if offline() {
	fmt.Println("No network connection. Cannot get RSS feed")
	return
}

Resulting in a much nicer message if offline (no panics).


Roger Guldbrandsen

Written by Roger Guldbrandsen.