2022-12-31 12:30:26 -05:00
|
|
|
package hackernews
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-01-02 23:47:52 -05:00
|
|
|
"errors"
|
2023-01-04 21:41:41 -05:00
|
|
|
"fmt"
|
2023-01-10 19:03:42 -05:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2022-12-31 12:30:26 -05:00
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2022-12-31 13:50:55 -05:00
|
|
|
md "github.com/JohannesKaufmann/html-to-markdown"
|
2022-12-31 12:30:26 -05:00
|
|
|
hn "github.com/hermanschaaf/hackernews"
|
2023-01-06 19:46:41 -05:00
|
|
|
"github.com/mrusme/neonmodem/models/author"
|
|
|
|
"github.com/mrusme/neonmodem/models/forum"
|
|
|
|
"github.com/mrusme/neonmodem/models/post"
|
|
|
|
"github.com/mrusme/neonmodem/models/reply"
|
|
|
|
"github.com/mrusme/neonmodem/system/adapter"
|
2022-12-31 12:30:26 -05:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
type System struct {
|
2023-01-01 22:51:16 -05:00
|
|
|
ID int
|
2022-12-31 12:30:26 -05:00
|
|
|
config map[string]interface{}
|
|
|
|
logger *zap.SugaredLogger
|
|
|
|
client *hn.Client
|
|
|
|
}
|
|
|
|
|
2023-01-01 22:51:16 -05:00
|
|
|
func (sys *System) GetID() int {
|
|
|
|
return sys.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) SetID(id int) {
|
|
|
|
sys.ID = id
|
|
|
|
}
|
|
|
|
|
2022-12-31 12:30:26 -05:00
|
|
|
func (sys *System) GetConfig() map[string]interface{} {
|
|
|
|
return sys.config
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) SetConfig(cfg *map[string]interface{}) {
|
|
|
|
sys.config = *cfg
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) SetLogger(logger *zap.SugaredLogger) {
|
|
|
|
sys.logger = logger
|
|
|
|
}
|
|
|
|
|
2023-01-05 00:23:25 -05:00
|
|
|
func (sys *System) GetCapabilities() adapter.Capabilities {
|
2022-12-31 12:30:26 -05:00
|
|
|
var caps []adapter.Capability
|
|
|
|
|
2023-01-05 00:04:20 -05:00
|
|
|
caps = append(caps,
|
2023-01-06 23:48:21 -05:00
|
|
|
// TODO: Requires accounts
|
|
|
|
// adapter.Capability{
|
|
|
|
// ID: "connect:multiple",
|
|
|
|
// Name: "Connect Multiple",
|
|
|
|
// },
|
2023-01-05 12:12:14 -05:00
|
|
|
adapter.Capability{
|
|
|
|
ID: "list:forums",
|
|
|
|
Name: "List Forums",
|
|
|
|
},
|
2023-01-05 00:04:20 -05:00
|
|
|
adapter.Capability{
|
|
|
|
ID: "list:posts",
|
|
|
|
Name: "List Posts",
|
|
|
|
},
|
2023-01-05 13:24:32 -05:00
|
|
|
// TODO: https://github.com/hermanschaaf/hackernews/issues/1
|
|
|
|
// adapter.Capability{
|
|
|
|
// ID: "create:post",
|
|
|
|
// Name: "Create Post",
|
|
|
|
// },
|
2023-01-05 00:04:20 -05:00
|
|
|
adapter.Capability{
|
|
|
|
ID: "list:replies",
|
|
|
|
Name: "List Replies",
|
|
|
|
},
|
2023-01-05 13:24:32 -05:00
|
|
|
// TODO: https://github.com/hermanschaaf/hackernews/issues/1
|
|
|
|
// adapter.Capability{
|
|
|
|
// ID: "create:reply",
|
|
|
|
// Name: "Create Reply",
|
|
|
|
// },
|
2023-01-05 00:04:20 -05:00
|
|
|
)
|
2022-12-31 12:30:26 -05:00
|
|
|
|
|
|
|
return caps
|
|
|
|
}
|
|
|
|
|
2023-01-04 21:41:41 -05:00
|
|
|
func (sys *System) FilterValue() string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"Hacker News https://news.ycombinator.com",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) Title() string {
|
2023-01-04 23:59:37 -05:00
|
|
|
return "news.ycombinator.com"
|
2023-01-04 21:41:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) Description() string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"Hacker News",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-12-31 12:30:26 -05:00
|
|
|
func (sys *System) Load() error {
|
2023-01-10 19:03:42 -05:00
|
|
|
var httpClient *http.Client = nil
|
2023-01-10 19:21:51 -05:00
|
|
|
var httpTransport *http.Transport = http.DefaultTransport.(*http.Transport).
|
|
|
|
Clone()
|
2023-01-10 19:03:42 -05:00
|
|
|
|
|
|
|
proxy := sys.config["proxy"].(string)
|
|
|
|
if proxy != "" {
|
|
|
|
proxyURL, err := url.Parse(proxy)
|
|
|
|
if err != nil {
|
|
|
|
sys.logger.Error(err)
|
|
|
|
} else {
|
|
|
|
sys.logger.Debugf("setting up http proxy transport: %s\n",
|
|
|
|
proxyURL.String())
|
|
|
|
httpTransport = &http.Transport{
|
|
|
|
Proxy: http.ProxyURL(proxyURL),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
httpClient = &http.Client{
|
|
|
|
Transport: httpTransport,
|
|
|
|
Timeout: time.Second * 10,
|
|
|
|
}
|
|
|
|
|
|
|
|
sys.client = hn.NewClient(hn.WithHTTPClient(httpClient))
|
2022-12-31 12:30:26 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-05 11:43:52 -05:00
|
|
|
func (sys *System) ListForums() ([]forum.Forum, error) {
|
|
|
|
return []forum.Forum{
|
|
|
|
{
|
|
|
|
ID: "top",
|
|
|
|
Name: "Top HN Stories",
|
2023-01-05 20:47:20 -05:00
|
|
|
Info: "Top stories on Hacker News",
|
2023-01-05 11:43:52 -05:00
|
|
|
SysIDX: sys.ID,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "best",
|
|
|
|
Name: "Best HN Stories",
|
2023-01-05 20:47:20 -05:00
|
|
|
Info: "Best stories on Hacker News",
|
2023-01-05 11:43:52 -05:00
|
|
|
SysIDX: sys.ID,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "new",
|
|
|
|
Name: "New HN Stories",
|
2023-01-05 20:47:20 -05:00
|
|
|
Info: "New stories on Hacker News",
|
2023-01-05 11:43:52 -05:00
|
|
|
SysIDX: sys.ID,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "ask",
|
|
|
|
Name: "Ask HN",
|
2023-01-05 20:47:20 -05:00
|
|
|
Info: "Ask Hacker News about the world",
|
2023-01-05 11:43:52 -05:00
|
|
|
SysIDX: sys.ID,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "show",
|
|
|
|
Name: "Show HN",
|
2023-01-05 20:47:20 -05:00
|
|
|
Info: "Show Hacker News something awesome",
|
2023-01-05 11:43:52 -05:00
|
|
|
SysIDX: sys.ID,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "jobs",
|
|
|
|
Name: "Jobs HN",
|
2023-01-05 20:47:20 -05:00
|
|
|
Info: "... because we can't *all* become Astronauts",
|
2023-01-05 11:43:52 -05:00
|
|
|
SysIDX: sys.ID,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) ListPosts(forumID string) ([]post.Post, error) {
|
|
|
|
var stories []int
|
|
|
|
var err error
|
|
|
|
|
|
|
|
switch forumID {
|
|
|
|
case "top":
|
|
|
|
stories, err = sys.client.TopStories(context.Background())
|
|
|
|
case "best":
|
|
|
|
stories, err = sys.client.BestStories(context.Background())
|
|
|
|
case "ask":
|
|
|
|
stories, err = sys.client.AskStories(context.Background())
|
|
|
|
case "show":
|
|
|
|
stories, err = sys.client.ShowStories(context.Background())
|
|
|
|
case "jobs":
|
|
|
|
stories, err = sys.client.JobStories(context.Background())
|
|
|
|
default:
|
|
|
|
stories, err = sys.client.NewStories(context.Background())
|
|
|
|
}
|
2022-12-31 12:30:26 -05:00
|
|
|
if err != nil {
|
|
|
|
return []post.Post{}, err
|
|
|
|
}
|
|
|
|
|
2022-12-31 13:50:55 -05:00
|
|
|
converter := md.NewConverter("", true, nil)
|
|
|
|
|
2022-12-31 12:30:26 -05:00
|
|
|
var models []post.Post
|
|
|
|
for _, story := range stories[0:10] {
|
|
|
|
i, err := sys.client.GetItem(context.Background(), story)
|
|
|
|
if err != nil {
|
|
|
|
sys.logger.Error(err)
|
2023-01-05 12:49:08 -05:00
|
|
|
// TODO: Handle error
|
2022-12-31 12:30:26 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
t := "post"
|
|
|
|
body := i.Text
|
|
|
|
if i.URL != "" {
|
|
|
|
t = "url"
|
|
|
|
body = i.URL
|
2022-12-31 13:50:55 -05:00
|
|
|
} else {
|
|
|
|
bodyMd, err := converter.ConvertString(i.Text)
|
|
|
|
if err == nil {
|
|
|
|
body = bodyMd
|
|
|
|
}
|
2022-12-31 12:30:26 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
createdAt := time.Unix(int64(i.Time), 0)
|
|
|
|
lastCommentedAt := createdAt
|
|
|
|
|
|
|
|
var replies []reply.Reply
|
|
|
|
for _, commentID := range i.Kids {
|
|
|
|
replies = append(replies, reply.Reply{
|
|
|
|
ID: strconv.Itoa(commentID),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
models = append(models, post.Post{
|
|
|
|
ID: strconv.Itoa(i.ID),
|
|
|
|
|
|
|
|
Subject: i.Title,
|
|
|
|
Body: body,
|
|
|
|
|
|
|
|
Type: t,
|
|
|
|
|
|
|
|
Pinned: false,
|
|
|
|
Closed: i.Deleted,
|
|
|
|
|
|
|
|
CreatedAt: createdAt,
|
|
|
|
LastCommentedAt: lastCommentedAt,
|
|
|
|
|
|
|
|
Author: author.Author{
|
|
|
|
ID: i.By,
|
|
|
|
Name: i.By,
|
|
|
|
},
|
|
|
|
|
|
|
|
Forum: forum.Forum{
|
|
|
|
ID: "new",
|
|
|
|
Name: "New",
|
2023-01-05 11:43:52 -05:00
|
|
|
|
|
|
|
SysIDX: sys.ID,
|
2022-12-31 12:30:26 -05:00
|
|
|
},
|
|
|
|
|
2023-01-10 14:36:07 -05:00
|
|
|
// TODO: Implement chunks loading
|
|
|
|
TotalReplies: 0,
|
|
|
|
CurrentRepliesStartIDX: -1,
|
|
|
|
Replies: replies,
|
2022-12-31 12:30:26 -05:00
|
|
|
|
2023-01-05 15:26:27 -05:00
|
|
|
URL: fmt.Sprintf("https://news.ycombinator.com/item?id=%d", i.ID),
|
|
|
|
|
2023-01-01 22:51:16 -05:00
|
|
|
SysIDX: sys.ID,
|
2022-12-31 12:30:26 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return models, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) LoadPost(p *post.Post) error {
|
2023-01-05 23:11:55 -05:00
|
|
|
p.Replies = []reply.Reply{}
|
2022-12-31 13:50:55 -05:00
|
|
|
return sys.loadReplies(&p.Replies)
|
2022-12-31 13:42:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) loadReplies(replies *[]reply.Reply) error {
|
2022-12-31 13:50:55 -05:00
|
|
|
converter := md.NewConverter("", true, nil)
|
2022-12-31 13:42:30 -05:00
|
|
|
for r := 0; r < len(*replies); r++ {
|
|
|
|
re := &(*replies)[r]
|
2022-12-31 12:30:26 -05:00
|
|
|
|
2022-12-31 13:42:30 -05:00
|
|
|
id, err := strconv.Atoi(re.ID)
|
2022-12-31 12:30:26 -05:00
|
|
|
if err != nil {
|
|
|
|
sys.logger.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
i, err := sys.client.GetItem(context.Background(), id)
|
|
|
|
if err != nil {
|
|
|
|
sys.logger.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-12-31 13:42:30 -05:00
|
|
|
if i.Deleted || i.Dead {
|
|
|
|
re.Deleted = true
|
|
|
|
}
|
|
|
|
|
2022-12-31 12:30:26 -05:00
|
|
|
createdAt := time.Unix(int64(i.Time), 0)
|
|
|
|
|
2022-12-31 13:42:30 -05:00
|
|
|
re.Body = i.Text
|
2022-12-31 13:50:55 -05:00
|
|
|
bodyMd, err := converter.ConvertString(i.Text)
|
|
|
|
if err == nil {
|
|
|
|
re.Body = bodyMd
|
|
|
|
}
|
2022-12-31 12:30:26 -05:00
|
|
|
|
2022-12-31 13:42:30 -05:00
|
|
|
re.CreatedAt = createdAt
|
2022-12-31 12:30:26 -05:00
|
|
|
|
2022-12-31 13:42:30 -05:00
|
|
|
re.Author = author.Author{
|
2022-12-31 12:30:26 -05:00
|
|
|
ID: i.By,
|
|
|
|
Name: i.By,
|
|
|
|
}
|
2022-12-31 13:42:30 -05:00
|
|
|
|
2023-01-01 22:51:16 -05:00
|
|
|
re.SysIDX = sys.ID
|
|
|
|
|
2022-12-31 13:42:30 -05:00
|
|
|
for _, commentID := range i.Kids {
|
|
|
|
re.Replies = append(re.Replies, reply.Reply{
|
|
|
|
ID: strconv.Itoa(commentID),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := sys.loadReplies(&re.Replies); err != nil {
|
|
|
|
sys.logger.Error(err)
|
|
|
|
}
|
2022-12-31 12:30:26 -05:00
|
|
|
}
|
2022-12-31 13:42:30 -05:00
|
|
|
|
2022-12-31 12:30:26 -05:00
|
|
|
return nil
|
|
|
|
}
|
2023-01-01 22:51:16 -05:00
|
|
|
|
|
|
|
func (sys *System) CreatePost(p *post.Post) error {
|
2023-01-02 23:47:52 -05:00
|
|
|
return errors.New("Sorry, this feature isn't available yet for Hacker News!")
|
2023-01-01 22:51:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) CreateReply(r *reply.Reply) error {
|
2023-01-02 23:47:52 -05:00
|
|
|
return errors.New("Sorry, this feature isn't available yet for Hacker News!")
|
2023-01-01 22:51:16 -05:00
|
|
|
}
|