mastodon-markdown-archive/client/client.go

181 lines
3.8 KiB
Go
Raw Normal View History

2024-05-13 08:47:26 +00:00
package client
import (
"fmt"
"net/url"
"strings"
)
type ClientOptions struct {
Visibility string
Threaded bool
}
2024-05-13 08:47:26 +00:00
type Client struct {
2024-05-18 21:35:15 +00:00
handle string
baseURL string
filters PostsFilter
account Account
// Map of Post.InReplyToId:PostId. Tracks the replies on a 1:1 basis.
replies map[string]string
// List of Post.Id. Tracks posts whose parent is not within the bounds of
// the returned posts.
orphans []string
// Map of Post.Id:*Post.
postIdMap map[string]*Post
// List of Post.Id. Tracks the posts which will be written as individual files.
output []string
options ClientOptions
2024-05-13 08:47:26 +00:00
}
func New(userURL string, filters PostsFilter, opts ClientOptions) (Client, error) {
2024-05-13 08:47:26 +00:00
var client Client
parsedURL, err := url.Parse(userURL)
if err != nil {
return client, fmt.Errorf("error parsing user url: %w", err)
}
baseURL := fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host)
acc := strings.TrimPrefix(parsedURL.Path, "/")
handle := strings.TrimPrefix(acc, "@")
2024-05-18 14:08:07 +00:00
account, err := FetchAccount(baseURL, handle)
2024-05-13 08:47:26 +00:00
if err != nil {
return client, err
}
2024-05-18 14:08:07 +00:00
posts, err := FetchPosts(baseURL, account.Id, filters)
2024-05-13 08:47:26 +00:00
if err != nil {
return client, err
}
2024-05-18 21:35:15 +00:00
postIdMap := make(map[string]*Post)
2024-05-18 21:48:20 +00:00
replies := make(map[string]string)
2024-05-13 08:47:26 +00:00
var orphans []string
2024-05-18 21:35:15 +00:00
var output []string
for i := range posts {
post := posts[i]
postIdMap[post.Id] = &post
if !opts.Threaded && !post.ShouldSkip(opts.Visibility) {
2024-05-18 21:35:15 +00:00
output = append(output, post.Id)
}
}
2024-05-13 08:47:26 +00:00
client = Client{
2024-05-18 13:22:23 +00:00
baseURL: baseURL,
handle: handle,
filters: filters,
account: account,
2024-05-18 21:35:15 +00:00
postIdMap: postIdMap,
2024-05-18 21:48:20 +00:00
replies: replies,
2024-05-18 13:22:23 +00:00
orphans: orphans,
2024-05-18 21:35:15 +00:00
output: output,
options: opts,
2024-05-13 08:47:26 +00:00
}
if opts.Threaded {
for _, post := range posts {
client.threadPost(post.Id)
}
if len(client.orphans) > 0 {
if err := client.buildOrphans(); err != nil {
return client, nil
}
}
2024-05-18 21:48:20 +00:00
}
return client, nil
}
2024-05-18 20:42:59 +00:00
2024-05-18 21:48:20 +00:00
func (c Client) Account() Account {
return c.account
}
2024-05-18 20:42:59 +00:00
2024-05-18 21:48:20 +00:00
func (c Client) Posts() []*Post {
var posts []*Post
2024-05-18 20:42:59 +00:00
2024-05-18 21:48:20 +00:00
for _, postId := range c.output {
posts = append(posts, c.postIdMap[postId])
}
2024-05-18 20:42:59 +00:00
2024-05-18 21:48:20 +00:00
return posts
}
2024-05-18 20:42:59 +00:00
2024-05-18 21:48:20 +00:00
func (c *Client) buildOrphans() error {
for _, postId := range c.orphans {
statusContext, err := FetchStatusContext(c.baseURL, postId)
2024-05-18 20:42:59 +00:00
2024-05-18 21:48:20 +00:00
if err != nil {
return err
}
2024-05-18 20:42:59 +00:00
var top Post;
// When building a thread from the status context endpoint,
// start from the greatest ancestor and add the other ancestors
// below it as descendants.
// Otherwise, use the orphan as the start.
if len(statusContext.Ancestors) > 0 {
top = statusContext.Ancestors[0]
for i := range statusContext.Ancestors[1:] {
post := statusContext.Ancestors[i+1]
if post.Account.Id != c.account.Id {
continue
}
c.postIdMap[post.Id] = &post
top.descendants = append(top.descendants, &post)
}
2024-05-18 20:42:59 +00:00
top.descendants = append(top.descendants, c.postIdMap[postId])
} else {
top = *c.postIdMap[postId]
2024-05-18 20:42:59 +00:00
}
2024-05-13 08:47:26 +00:00
2024-05-18 21:48:20 +00:00
for i := range statusContext.Descendants {
post := statusContext.Descendants[i]
if post.Account.Id != c.account.Id {
continue
}
2024-05-13 08:47:26 +00:00
2024-05-18 21:48:20 +00:00
c.postIdMap[post.Id] = &post
top.descendants = append(top.descendants, &post)
}
2024-05-18 13:22:23 +00:00
2024-05-18 21:48:20 +00:00
c.postIdMap[top.Id] = &top
c.output = append(c.output, top.Id)
2024-05-18 13:22:23 +00:00
}
2024-05-18 21:48:20 +00:00
return nil
2024-05-13 08:47:26 +00:00
}
2024-05-18 13:22:23 +00:00
func (c *Client) flushReplies(post *Post, descendants *[]*Post) {
if pid, ok := c.replies[post.Id]; ok {
2024-05-18 21:35:15 +00:00
reply := c.postIdMap[pid]
*descendants = append(*descendants, reply)
c.flushReplies(reply, descendants)
2024-05-13 08:47:26 +00:00
}
}
func (c *Client) threadPost(postId string) {
post := c.postIdMap[postId]
2024-05-13 08:47:26 +00:00
if post.InReplyToId == "" && !post.ShouldSkip(c.options.Visibility) {
c.flushReplies(post, &post.descendants)
c.output = append(c.output, post.Id)
return
2024-05-13 08:47:26 +00:00
}
2024-05-18 21:48:20 +00:00
if _, ok := c.postIdMap[post.InReplyToId]; ok {
c.replies[post.InReplyToId] = post.Id
} else {
c.orphans = append(c.orphans, post.Id)
2024-05-18 21:48:20 +00:00
}
2024-05-13 08:47:26 +00:00
}