website-feeds

Make RSS feeds of your favorite "vote on posts" websites

git clone https://code.pdelong.com/website-feeds.git

  1package lobsters
  2
  3// TODO: Add tests.
  4
  5import (
  6	"context"
  7	"encoding/json"
  8	"fmt"
  9	"log"
 10	"net/http"
 11	"time"
 12
 13	"website-feeds/model"
 14	"website-feeds/util"
 15)
 16
 17const (
 18	topPostsURL = "https://lobste.rs/hottest.json"
 19)
 20
 21type Client struct {
 22	client *http.Client
 23}
 24
 25func New() *Client {
 26	return &Client{
 27		client: util.NewHttpClient(),
 28	}
 29}
 30
 31func (*Client) CommentURL(post model.Post) string {
 32	return fmt.Sprintf("https://lobste.rs/s/%s", post.SiteUniqueID)
 33}
 34
 35func (*Client) URL() string {
 36	return "https://lobste.rs"
 37}
 38
 39func (c *Client) FetchPosts(
 40	ctx context.Context,
 41	numPosts int,
 42) ([]model.Post, error) {
 43	type rawPost struct {
 44		ID      string `json:"short_id"`
 45		Score   int    `json:"score"`
 46		Title   string `json:"title"`
 47		URL     string `json:"url"`
 48		Created string `json:"created_at"`
 49	}
 50
 51	req, err := http.NewRequestWithContext(ctx, "GET", topPostsURL, nil)
 52	if err != nil {
 53		return nil, err
 54	}
 55
 56	resp, err := c.client.Do(req)
 57	if err != nil {
 58		return nil, err
 59	}
 60	defer func() {
 61		if err := resp.Body.Close(); err != nil {
 62			log.Printf("failed to close body: %s", err)
 63		}
 64	}()
 65
 66	rawPosts := make([]rawPost, 0)
 67	err = json.NewDecoder(resp.Body).Decode(&rawPosts)
 68	if err != nil {
 69		return nil, err
 70	}
 71
 72	posts := make([]model.Post, len(rawPosts))
 73	for i, post := range rawPosts {
 74		created, err := time.Parse(
 75			"2006-01-02T15:04:05.999-07:00",
 76			post.Created,
 77		)
 78		if err != nil {
 79			return nil, err
 80		}
 81
 82		internalPost := post.URL == ""
 83		var url *string
 84		if !internalPost {
 85			// Clone string because it will be overwritten by the next loop.
 86			str := post.URL
 87			url = &str
 88		}
 89
 90		posts[i] = model.Post{
 91			SiteUniqueID: post.ID,
 92			Score:        post.Score,
 93			Title:        post.Title,
 94			URL:          url,
 95			Created:      created.UTC(),
 96		}
 97	}
 98	return posts, nil
 99}
100
101func (c *Client) DisplayName(
102	_ctx context.Context,
103) (string, error) {
104	return "Lobsters", nil
105}