You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
3.2 KiB
148 lines
3.2 KiB
package main |
|
|
|
import ( |
|
"bytes" |
|
"encoding/json" |
|
"fmt" |
|
"io/ioutil" |
|
"log" |
|
"net/http" |
|
"strconv" |
|
|
|
"golang.org/x/net/html" |
|
) |
|
|
|
type contentFetcher interface { |
|
fetchContent(url string) (content, error) |
|
} |
|
|
|
type content struct { |
|
title string |
|
excerpt string |
|
authors []string |
|
imageURL string |
|
wordCount int |
|
} |
|
|
|
type pocket struct { |
|
consumerKey string |
|
accessToken string |
|
} |
|
|
|
type Article struct { |
|
ItemId string `json:"item_id"` |
|
ResolvedId string `json:"resolved_id"` |
|
GivenUrl string `json:"given_url"` |
|
GivenTitle string `json:"given_title"` |
|
Title string `json:"title"` |
|
ResolvedUrl string `json:"resolved_url"` |
|
Excerpt string `json:"excerpt"` |
|
IsArticle string `json:"is_article"` |
|
WordCount string `json:"word_count"` |
|
Authors map[string]Author `json:"authors"` |
|
TopImageURL string `json:"top_image_url"` |
|
} |
|
|
|
type Author struct { |
|
AuthorID string `json:"author_id"` |
|
Name string `json:"name"` |
|
URL string `json:"url"` |
|
} |
|
|
|
func newPocket(consumerKey, accessToken string) *pocket { |
|
return &pocket{consumerKey, accessToken} |
|
} |
|
|
|
func (p *pocket) fetchContent(url string) (cont content, err error) { |
|
article, err := p.addItem(url) |
|
if err != nil { |
|
return |
|
} |
|
|
|
cont.title = article.Title |
|
cont.excerpt = article.Excerpt |
|
cont.wordCount, _ = strconv.Atoi(article.WordCount) |
|
for _, author := range article.Authors { |
|
cont.authors = append(cont.authors, author.Name) |
|
} |
|
cont.imageURL = article.TopImageURL |
|
return |
|
} |
|
|
|
func (p *pocket) addItem(url string) (Article, error) { |
|
body := map[string]string{ |
|
"access_token": p.accessToken, |
|
"consumer_key": p.consumerKey, |
|
"url": url, |
|
} |
|
jsonBody, err := json.Marshal(body) |
|
if err != nil { |
|
return Article{}, err |
|
} |
|
|
|
res, err := http.Post("https://getpocket.com/v3/add", "application/json", bytes.NewBuffer(jsonBody)) |
|
if err != nil { |
|
return Article{}, err |
|
} |
|
defer res.Body.Close() |
|
resBody, err := ioutil.ReadAll(res.Body) |
|
if err != nil { |
|
return Article{}, fmt.Errorf("Unable to retrieve items: %v", err) |
|
} |
|
if res.StatusCode != http.StatusOK { |
|
return Article{}, fmt.Errorf("Could not add item to pocket (%d): %s", res.StatusCode, string(resBody)) |
|
} |
|
|
|
var resItem struct { |
|
Item Article `json:"item"` |
|
} |
|
json.Unmarshal(resBody, &resItem) |
|
return resItem.Item, nil |
|
} |
|
|
|
type dummyContentFetcher struct{} |
|
|
|
func (dc *dummyContentFetcher) fetchContent(url string) (content, error) { |
|
return content{ |
|
title: getTitle(url), |
|
}, nil |
|
} |
|
|
|
func getTitle(url string) string { |
|
resp, err := http.Get(url) |
|
if err != nil { |
|
log.Printf("Error fetching %s: %v", url, err) |
|
return url |
|
} |
|
defer resp.Body.Close() |
|
|
|
doc, err := html.Parse(resp.Body) |
|
if err != nil { |
|
log.Printf("Error parsing %s: %v", url, err) |
|
return url |
|
} |
|
title, ok := traverse(doc) |
|
if ok { |
|
return title |
|
} |
|
return url |
|
} |
|
|
|
func traverse(n *html.Node) (string, bool) { |
|
if isTitleElement(n) && n.FirstChild != nil { |
|
return n.FirstChild.Data, true |
|
} |
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling { |
|
result, ok := traverse(c) |
|
if ok { |
|
return result, ok |
|
} |
|
} |
|
|
|
return "", false |
|
} |
|
|
|
func isTitleElement(n *html.Node) bool { |
|
return n.Type == html.ElementNode && n.Data == "title" |
|
}
|
|
|