2024-05-13 08:47:26 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2024-05-19 19:29:36 +00:00
|
|
|
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.
|
2024-05-19 19:29:36 +00:00
|
|
|
output []string
|
|
|
|
options ClientOptions
|
2024-05-13 08:47:26 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 19:29:36 +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
|
2024-05-19 19:29:36 +00:00
|
|
|
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,
|
2024-05-19 19:29:36 +00:00
|
|
|
options: opts,
|
2024-05-13 08:47:26 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 19:29:36 +00:00
|
|
|
if opts.Threaded {
|
2024-05-19 15:46:14 +00:00
|
|
|
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
|
|
|
|
2024-08-03 06:33:33 +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]
|
2024-08-03 08:38:18 +00:00
|
|
|
if post.Account.Id != c.account.Id {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-08-03 06:33:33 +00:00
|
|
|
c.postIdMap[post.Id] = &post
|
|
|
|
top.descendants = append(top.descendants, &post)
|
|
|
|
}
|
2024-05-18 20:42:59 +00:00
|
|
|
|
2024-08-03 06:33:33 +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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-19 15:46:14 +00:00
|
|
|
func (c *Client) threadPost(postId string) {
|
|
|
|
post := c.postIdMap[postId]
|
2024-05-13 08:47:26 +00:00
|
|
|
|
2024-05-19 19:29:36 +00:00
|
|
|
if post.InReplyToId == "" && !post.ShouldSkip(c.options.Visibility) {
|
2024-05-19 15:46:14 +00:00
|
|
|
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
|
|
|
|
2024-05-19 15:46:14 +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
|
|
|
}
|