1package lobsters23// TODO: Add tests.45import (6 "context"7 "encoding/json"8 "fmt"9 "log"10 "net/http"11 "time"1213 "website-feeds/model"14 "website-feeds/util"15)1617const (18 topPostsURL = "https://lobste.rs/hottest.json"19)2021type Client struct {22 client *http.Client23}2425func New() *Client {26 return &Client{27 client: util.NewHttpClient(),28 }29}3031func (*Client) CommentURL(post model.Post) string {32 return fmt.Sprintf("https://lobste.rs/s/%s", post.SiteUniqueID)33}3435func (*Client) URL() string {36 return "https://lobste.rs"37}3839func (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 }5051 req, err := http.NewRequestWithContext(ctx, "GET", topPostsURL, nil)52 if err != nil {53 return nil, err54 }5556 resp, err := c.client.Do(req)57 if err != nil {58 return nil, err59 }60 defer func() {61 if err := resp.Body.Close(); err != nil {62 log.Printf("failed to close body: %s", err)63 }64 }()6566 rawPosts := make([]rawPost, 0)67 err = json.NewDecoder(resp.Body).Decode(&rawPosts)68 if err != nil {69 return nil, err70 }7172 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, err80 }8182 internalPost := post.URL == ""83 var url *string84 if !internalPost {85 // Clone string because it will be overwritten by the next loop.86 str := post.URL87 url = &str88 }8990 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, nil99}100101func (c *Client) DisplayName(102 _ctx context.Context,103) (string, error) {104 return "Lobsters", nil105}