small pixel drawing of a pufferfish jrss

main.go

package main

import (
	"encoding/csv"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"sort"
	"strings"
	"sync"
	"text/template"
	"time"

	"github.com/SlyMarbo/rss"
)

type FeedItem struct {
	FeedTitle string
	PostTitle string
	Link      string
	Date      time.Time
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	// replace this with hardcoded list from j3s.sh
	scrapedFeeds, err := makeFeedList(os.Args[1])
	if err != nil {
		fmt.Fprintf(w, "makeList error: %s", err)
		log.Fatal(err)
	}

	feeds := make([]rss.Feed, 0)
	var feed *rss.Feed
	fetchFailures := make([]error, 0)

	var wg sync.WaitGroup
	var m sync.Mutex
	wg.Add(len(scrapedFeeds))
	for _, f := range scrapedFeeds {
		go func(f string) {
			defer wg.Done()
			feed, err = getFeed(f)
			if err != nil {
				// break loop in case of
				// feed fetching failure
				log.Print(err)
				err = fmt.Errorf("%s<br>%s", f, err)
				m.Lock()
				defer m.Unlock()
				fetchFailures = append(fetchFailures, err)
				return
			}
			feeds = append(feeds, *feed)
		}(f)
	}
	wg.Wait()

	if len(fetchFailures) > 0 {
		for _, f := range fetchFailures {
			// put this on the website eventually??
			log.Printf("failed to fetch %s", f)
		}
	}

	var feedItems []FeedItem
	for _, f := range feeds {
		for _, i := range f.Items {
			var fi FeedItem
			fi.FeedTitle = f.Title
			fi.PostTitle = i.Title
			fi.Link = i.Link
			fi.Date = i.Date
			feedItems = append(feedItems, fi)
			//			fmt.Fprintf(w, "<tr>")
			//			fmt.Fprintf(w, "<td>%s: <a href=\"%s\">%s</a> <small>%s</small></td>",
			//				f.Title, i.Link, i.Title, i.Date)
			// fmt.Fprintf(w, "</tr>")
		}
	}

	sort.Slice(feedItems, func(i, j int) bool {
		return feedItems[i].Date.After(feedItems[j].Date)
	})

	// here we goooOooOoOoOooOooOo
	// ooOooOOoooOOooooOOOOOooooOO
	const tpl = `
	<!DOCTYPE html>
	<html>
		<head>
			<meta charset="UTF-8">
			<title>jes's personal feed list</title>
		</head>
		<body>
			<table><tbody>
				{{range .}}
				<tr>
				<td>
					{{.FeedTitle}}: <a href="{{.Link}}">{{.PostTitle}}</a> <small>{{.Date}}</small>
				</td>
				</tr>
				{{end}}
			</tbody></table>
		</body>
	</html>`

	t, err := template.New("jrss").Parse(tpl)
	if err != nil {
		log.Fatal(err)
	}

	err = t.Execute(w, feedItems)
	if err != nil {
		log.Print(err)
	}
}

func getFeed(url string) (*rss.Feed, error) {
	feed, err := rss.Fetch(url)
	if err != nil {
		return nil, err
	}
	return feed, nil
}

func makeFeedList(url string) ([]string, error) {
	res, err := http.Get(url)
	if err != nil {
		err = fmt.Errorf("makeFeedList http.Get: %w", err)
		return nil, err
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		err = fmt.Errorf("makeFeedList failed to read http body: %w", err)
		return nil, err
	}
	r := csv.NewReader(strings.NewReader(string(body)))
	feeds := make([]string, 0)
	for {
		record, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			err = fmt.Errorf("makeFeedList record parsing error: %w\nvalue: %s", err, record)
			return nil, err
		}
		// [0] == url to a feed (item 0 of the csv)
		feeds = append(feeds, record[0])
	}
	return feeds, err
}

func faviconHandler(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
}

func main() {
	if len(os.Args) == 1 {
		log.Fatal("Supply your feed list as ARG1.\nExample: go run main.go https://j3s.sh/feeds.txt")
	}

	http.HandleFunc("/favicon.ico", faviconHandler)
	http.HandleFunc("/", rootHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}