2022-12-28 22:22:36 -05:00
|
|
|
package discourse
|
|
|
|
|
|
|
|
import (
|
2022-12-29 23:02:19 -05:00
|
|
|
"context"
|
2023-01-04 21:41:41 -05:00
|
|
|
"fmt"
|
2023-01-04 23:59:37 -05:00
|
|
|
"net/url"
|
2022-12-30 01:44:44 -05:00
|
|
|
"strconv"
|
2022-12-30 02:05:48 -05:00
|
|
|
"time"
|
2022-12-29 23:02:19 -05:00
|
|
|
|
2022-12-30 21:14:00 -05:00
|
|
|
md "github.com/JohannesKaufmann/html-to-markdown"
|
2022-12-30 02:05:48 -05:00
|
|
|
"github.com/araddon/dateparse"
|
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"
|
|
|
|
"github.com/mrusme/neonmodem/system/discourse/api"
|
2022-12-29 22:03:38 -05:00
|
|
|
"go.uber.org/zap"
|
2022-12-28 22:22:36 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type System struct {
|
2023-01-10 18:56:02 -05:00
|
|
|
ID int
|
|
|
|
config map[string]interface{}
|
|
|
|
logger *zap.SugaredLogger
|
|
|
|
client *api.Client
|
|
|
|
clientCfg api.ClientConfig
|
2022-12-29 14:35:01 -05:00
|
|
|
}
|
|
|
|
|
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-29 14:35:01 -05:00
|
|
|
func (sys *System) GetConfig() map[string]interface{} {
|
|
|
|
return sys.config
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) SetConfig(cfg *map[string]interface{}) {
|
|
|
|
sys.config = *cfg
|
2022-12-28 22:22:36 -05:00
|
|
|
}
|
|
|
|
|
2022-12-29 22:03:38 -05:00
|
|
|
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-30 02:48:53 -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
|
|
|
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",
|
|
|
|
},
|
|
|
|
adapter.Capability{
|
|
|
|
ID: "create:post",
|
|
|
|
Name: "Create Post",
|
|
|
|
},
|
|
|
|
adapter.Capability{
|
|
|
|
ID: "list:replies",
|
|
|
|
Name: "List Replies",
|
|
|
|
},
|
|
|
|
adapter.Capability{
|
|
|
|
ID: "create:reply",
|
|
|
|
Name: "Create Reply",
|
|
|
|
},
|
|
|
|
)
|
2022-12-30 02:48:53 -05:00
|
|
|
|
|
|
|
return caps
|
|
|
|
}
|
|
|
|
|
2023-01-04 21:41:41 -05:00
|
|
|
func (sys *System) FilterValue() string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"Discourse %s",
|
|
|
|
sys.config["url"],
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) Title() string {
|
2023-01-04 23:59:37 -05:00
|
|
|
sysUrl := sys.config["url"].(string)
|
|
|
|
u, err := url.Parse(sysUrl)
|
|
|
|
if err != nil {
|
|
|
|
return sysUrl
|
|
|
|
}
|
|
|
|
|
|
|
|
return u.Hostname()
|
2023-01-04 21:41:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) Description() string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"Discourse",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-12-28 22:22:36 -05:00
|
|
|
func (sys *System) Load() error {
|
2022-12-30 00:43:32 -05:00
|
|
|
url := sys.config["url"]
|
|
|
|
if url == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
credentials := make(map[string]string)
|
|
|
|
for k, v := range (sys.config["credentials"]).(map[string]interface{}) {
|
|
|
|
credentials[k] = v.(string)
|
|
|
|
}
|
|
|
|
|
2023-01-10 18:56:02 -05:00
|
|
|
sys.clientCfg = api.NewDefaultClientConfig(
|
|
|
|
url.(string),
|
|
|
|
sys.config["proxy"].(string),
|
|
|
|
credentials,
|
|
|
|
sys.logger,
|
|
|
|
)
|
|
|
|
sys.client = api.NewClient(&sys.clientCfg)
|
2022-12-29 14:35:01 -05:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-05 11:43:52 -05:00
|
|
|
func (sys *System) ListForums() ([]forum.Forum, error) {
|
|
|
|
var models []forum.Forum
|
|
|
|
|
|
|
|
cats, err := sys.client.Categories.List(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
return []forum.Forum{}, err
|
|
|
|
}
|
|
|
|
|
2023-01-10 19:37:07 -05:00
|
|
|
models = sys.recurseForums(&cats.CategoryList.Categories, nil)
|
2023-01-05 11:43:52 -05:00
|
|
|
|
2023-01-10 19:37:07 -05:00
|
|
|
return models, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) recurseForums(cats *[]api.CategoryModel, parent *forum.Forum) []forum.Forum {
|
|
|
|
var models []forum.Forum
|
|
|
|
|
|
|
|
sys.logger.Debugf("recursing categories: %d\n", len(*cats))
|
|
|
|
for i := 0; i < len(*cats); i++ {
|
|
|
|
sys.logger.Debugf("adding category: %s\n", (*cats)[i].Name)
|
|
|
|
|
|
|
|
var name = (*cats)[i].Name
|
|
|
|
if parent != nil {
|
|
|
|
name = fmt.Sprintf("%s / %s", parent.Name, name)
|
|
|
|
}
|
|
|
|
f := forum.Forum{
|
|
|
|
ID: strconv.Itoa((*cats)[i].ID),
|
|
|
|
Name: name,
|
|
|
|
|
|
|
|
Info: (*cats)[i].Description,
|
2023-01-05 20:47:20 -05:00
|
|
|
|
2023-01-05 11:43:52 -05:00
|
|
|
SysIDX: sys.ID,
|
2023-01-10 19:37:07 -05:00
|
|
|
}
|
|
|
|
models = append(models, f)
|
|
|
|
|
|
|
|
models = append(models, sys.recurseForums(&(*cats)[i].SubcategoryList, &f)...)
|
2023-01-05 11:43:52 -05:00
|
|
|
}
|
|
|
|
|
2023-01-10 19:37:07 -05:00
|
|
|
return models
|
2023-01-05 11:43:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) ListPosts(forumID string) ([]post.Post, error) {
|
|
|
|
var catSlug string = ""
|
|
|
|
var catID int = -1
|
|
|
|
var err error
|
|
|
|
|
2022-12-30 23:57:03 -05:00
|
|
|
cats, err := sys.client.Categories.List(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
return []post.Post{}, err
|
|
|
|
}
|
|
|
|
|
2023-01-05 11:43:52 -05:00
|
|
|
if forumID != "" {
|
|
|
|
catID, err = strconv.Atoi(forumID)
|
|
|
|
if err != nil {
|
|
|
|
return []post.Post{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, cat := range cats.CategoryList.Categories {
|
|
|
|
if cat.ID == catID {
|
|
|
|
catSlug = cat.Slug
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
items, err := sys.client.Topics.ListLatest(context.Background(), catSlug, catID)
|
2022-12-29 23:02:19 -05:00
|
|
|
if err != nil {
|
|
|
|
return []post.Post{}, err
|
|
|
|
}
|
|
|
|
|
2022-12-29 23:47:16 -05:00
|
|
|
var models []post.Post
|
|
|
|
for _, i := range (*items).TopicList.Topics {
|
2022-12-30 01:44:44 -05:00
|
|
|
var userName string = ""
|
|
|
|
for _, u := range (*items).Users {
|
|
|
|
if u.ID == i.Posters[0].UserID {
|
|
|
|
userName = u.Name
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-30 02:05:48 -05:00
|
|
|
createdAt, err := dateparse.ParseAny(i.CreatedAt)
|
|
|
|
if err != nil {
|
|
|
|
createdAt = time.Now() // TODO: Errrr
|
|
|
|
}
|
|
|
|
lastCommentedAt, err := dateparse.ParseAny(i.LastPostedAt)
|
|
|
|
if err != nil {
|
|
|
|
lastCommentedAt = time.Now() // TODO: Errrrr
|
|
|
|
}
|
|
|
|
|
2022-12-30 23:57:03 -05:00
|
|
|
var forumName string = ""
|
|
|
|
for _, cat := range cats.CategoryList.Categories {
|
|
|
|
if cat.ID == i.CategoryID {
|
|
|
|
forumName = cat.Name
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, subcat := range cat.SubcategoryList {
|
|
|
|
if subcat.ID == i.CategoryID {
|
|
|
|
forumName = subcat.Name
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-05 15:26:27 -05:00
|
|
|
cfg := sys.GetConfig()
|
|
|
|
baseURL := cfg["url"].(string)
|
|
|
|
|
2022-12-29 23:47:16 -05:00
|
|
|
models = append(models, post.Post{
|
2022-12-30 01:44:44 -05:00
|
|
|
ID: strconv.Itoa(i.ID),
|
|
|
|
|
2022-12-29 23:47:16 -05:00
|
|
|
Subject: i.Title,
|
2022-12-30 01:44:44 -05:00
|
|
|
|
|
|
|
Type: "post",
|
|
|
|
|
|
|
|
Pinned: i.Pinned,
|
|
|
|
Closed: i.Closed,
|
|
|
|
|
2022-12-30 02:05:48 -05:00
|
|
|
CreatedAt: createdAt,
|
|
|
|
LastCommentedAt: lastCommentedAt,
|
|
|
|
|
2022-12-30 01:44:44 -05:00
|
|
|
Author: author.Author{
|
|
|
|
ID: strconv.Itoa(i.Posters[0].UserID),
|
|
|
|
Name: userName,
|
|
|
|
},
|
2022-12-30 03:22:54 -05:00
|
|
|
|
2022-12-30 23:57:03 -05:00
|
|
|
Forum: forum.Forum{
|
|
|
|
ID: strconv.Itoa(i.CategoryID),
|
|
|
|
Name: forumName,
|
2023-01-05 11:43:52 -05:00
|
|
|
|
|
|
|
SysIDX: sys.ID,
|
2022-12-30 23:57:03 -05:00
|
|
|
},
|
|
|
|
|
2023-01-10 14:03:57 -05:00
|
|
|
TotalReplies: 0,
|
|
|
|
CurrentRepliesStartIDX: -1,
|
|
|
|
|
2023-01-05 15:26:27 -05:00
|
|
|
URL: fmt.Sprintf("%s/t/%d", baseURL, i.ID),
|
|
|
|
|
2023-01-01 22:51:16 -05:00
|
|
|
SysIDX: sys.ID,
|
2022-12-29 23:02:19 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-12-29 23:47:16 -05:00
|
|
|
return models, nil
|
2022-12-29 23:02:19 -05:00
|
|
|
}
|
2022-12-30 02:48:53 -05:00
|
|
|
|
|
|
|
func (sys *System) LoadPost(p *post.Post) error {
|
|
|
|
item, err := sys.client.Topics.Show(context.Background(), p.ID)
|
|
|
|
if err != nil {
|
2022-12-30 02:58:28 -05:00
|
|
|
return err
|
2022-12-30 02:48:53 -05:00
|
|
|
}
|
|
|
|
|
2023-01-10 13:04:47 -05:00
|
|
|
// API seems to return 20 posts by default. If the stream is greater than 20
|
|
|
|
// posts, we need to fetch the latest posts on our own, as we'd only get the
|
|
|
|
// first 20 posts otherwise.
|
2023-01-10 14:03:57 -05:00
|
|
|
p.TotalReplies = len(item.PostStream.Stream)
|
|
|
|
if p.TotalReplies > 20 {
|
2023-01-10 14:36:07 -05:00
|
|
|
var postIDs []int
|
|
|
|
|
2023-01-10 14:03:57 -05:00
|
|
|
if p.CurrentRepliesStartIDX == -1 ||
|
|
|
|
// Explain to me standard GoFmt logic:
|
|
|
|
p.CurrentRepliesStartIDX > (p.TotalReplies-20) {
|
|
|
|
p.CurrentRepliesStartIDX = (p.TotalReplies - 20)
|
|
|
|
// /)_-)
|
2023-01-10 14:36:07 -05:00
|
|
|
} else if p.CurrentRepliesStartIDX < -1 {
|
|
|
|
p.CurrentRepliesStartIDX = 0
|
2023-01-10 14:03:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if p.CurrentRepliesStartIDX > 0 {
|
|
|
|
postIDs = append(postIDs,
|
|
|
|
item.PostStream.Stream[0])
|
|
|
|
p.CurrentRepliesStartIDX++
|
|
|
|
}
|
|
|
|
postIDs = append(postIDs,
|
2023-01-10 14:36:07 -05:00
|
|
|
item.PostStream.Stream[p.CurrentRepliesStartIDX:(p.CurrentRepliesStartIDX+20)]...)
|
2023-01-10 14:03:57 -05:00
|
|
|
|
2023-01-10 13:04:47 -05:00
|
|
|
replies, err := sys.client.Topics.ShowPosts(
|
|
|
|
context.Background(),
|
|
|
|
p.ID,
|
2023-01-10 14:03:57 -05:00
|
|
|
postIDs,
|
2023-01-10 13:04:47 -05:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
sys.logger.Error(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
item.PostStream.Posts = replies.PostStream.Posts
|
|
|
|
}
|
|
|
|
|
2022-12-30 21:14:00 -05:00
|
|
|
converter := md.NewConverter("", true, nil)
|
|
|
|
|
2023-01-05 23:11:55 -05:00
|
|
|
p.Replies = []reply.Reply{}
|
2022-12-30 02:48:53 -05:00
|
|
|
for idx, i := range item.PostStream.Posts {
|
2022-12-30 21:14:00 -05:00
|
|
|
cookedMd, err := converter.ConvertString(i.Cooked)
|
|
|
|
if err != nil {
|
|
|
|
cookedMd = i.Cooked
|
|
|
|
}
|
|
|
|
|
2022-12-30 02:48:53 -05:00
|
|
|
if idx == 0 {
|
2022-12-30 21:14:00 -05:00
|
|
|
p.Body = cookedMd
|
2022-12-30 02:48:53 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
createdAt, err := dateparse.ParseAny(i.CreatedAt)
|
|
|
|
if err != nil {
|
|
|
|
createdAt = time.Now() // TODO: Errrrrr
|
|
|
|
}
|
|
|
|
p.Replies = append(p.Replies, reply.Reply{
|
2023-01-02 01:57:28 -05:00
|
|
|
ID: strconv.Itoa(i.ID),
|
|
|
|
InReplyTo: p.ID,
|
2023-01-05 17:40:18 -05:00
|
|
|
Index: idx,
|
2022-12-30 02:48:53 -05:00
|
|
|
|
2022-12-30 21:14:00 -05:00
|
|
|
Body: cookedMd,
|
2022-12-30 02:48:53 -05:00
|
|
|
|
|
|
|
CreatedAt: createdAt,
|
|
|
|
|
|
|
|
Author: author.Author{
|
|
|
|
ID: strconv.Itoa(i.UserID),
|
|
|
|
Name: i.Name,
|
|
|
|
},
|
2023-01-01 22:51:16 -05:00
|
|
|
|
|
|
|
SysIDX: sys.ID,
|
2022-12-30 02:48:53 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-01-01 22:51:16 -05:00
|
|
|
|
|
|
|
func (sys *System) CreatePost(p *post.Post) error {
|
|
|
|
categoryID, err := strconv.Atoi(p.Forum.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ap := api.CreatePostModel{
|
|
|
|
Title: p.Subject,
|
|
|
|
Raw: p.Body,
|
|
|
|
Category: categoryID,
|
2023-01-02 13:23:22 -05:00
|
|
|
CreatedAt: time.Now().Format(time.RFC3339Nano),
|
2023-01-01 22:51:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
cp, err := sys.client.Posts.Create(context.Background(), &ap)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
p.ID = strconv.Itoa(cp.ID)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sys *System) CreateReply(r *reply.Reply) error {
|
2023-01-02 23:30:22 -05:00
|
|
|
var err error
|
|
|
|
|
2023-01-02 01:57:28 -05:00
|
|
|
ID, err := strconv.Atoi(r.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-01-02 23:30:22 -05:00
|
|
|
|
|
|
|
var ap api.CreatePostModel
|
|
|
|
|
2023-01-05 17:40:18 -05:00
|
|
|
if r.Index == -1 {
|
2023-01-02 23:30:22 -05:00
|
|
|
// Looks like we're replying directly to a post
|
|
|
|
ap = api.CreatePostModel{
|
|
|
|
Raw: r.Body,
|
|
|
|
TopicID: ID,
|
|
|
|
CreatedAt: time.Now().Format(time.RFC3339Nano),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Apparently it's a reply to a comment in a post
|
2023-01-05 18:20:10 -05:00
|
|
|
inReplyTo, err := strconv.Atoi(r.InReplyTo)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-01-02 23:30:22 -05:00
|
|
|
ap = api.CreatePostModel{
|
|
|
|
Raw: r.Body,
|
|
|
|
TopicID: inReplyTo,
|
2023-01-05 18:20:10 -05:00
|
|
|
ReplyToPostNumber: r.Index + 1,
|
2023-01-02 23:30:22 -05:00
|
|
|
CreatedAt: time.Now().Format(time.RFC3339Nano),
|
|
|
|
}
|
2023-01-01 22:51:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
cp, err := sys.client.Posts.Create(context.Background(), &ap)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
r.ID = strconv.Itoa(cp.ID)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|