about summary refs log tree commit diff
path: root/src/lib/api/build-suggested-posts.ts
blob: 6250f4a9c033ec68b0d1c07e79d67269f35c1491 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import {RootStoreModel} from 'state/index'
import {
  AppBskyFeedFeedViewPost,
  AppBskyFeedGetAuthorFeed as GetAuthorFeed,
} from '@atproto/api'
type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost

async function getMultipleAuthorsPosts(
  rootStore: RootStoreModel,
  authors: string[],
  cursor: string | undefined = undefined,
  limit: number = 10,
) {
  const responses = await Promise.all(
    authors.map((author, index) =>
      rootStore.api.app.bsky.feed
        .getAuthorFeed({
          author,
          limit,
          before: cursor ? cursor.split(',')[index] : undefined,
        })
        .catch(_err => ({success: false, headers: {}, data: {feed: []}})),
    ),
  )
  return responses
}

function mergePosts(
  responses: GetAuthorFeed.Response[],
  {repostsOnly, bestOfOnly}: {repostsOnly?: boolean; bestOfOnly?: boolean},
) {
  let posts: AppBskyFeedFeedViewPost.Main[] = []

  if (bestOfOnly) {
    for (const res of responses) {
      if (res.success) {
        // filter the feed down to the post with the most upvotes
        res.data.feed = res.data.feed.reduce(
          (acc: AppBskyFeedFeedViewPost.Main[], v) => {
            if (!acc?.[0] && !v.reason) {
              return [v]
            }
            if (
              acc &&
              !v.reason &&
              v.post.upvoteCount > acc[0].post.upvoteCount
            ) {
              return [v]
            }
            return acc
          },
          [],
        )
      }
    }
  }

  // merge into one array
  for (const res of responses) {
    if (res.success) {
      posts = posts.concat(res.data.feed)
    }
  }

  // filter down to reposts of other users
  const uris = new Set()
  posts = posts.filter(p => {
    if (repostsOnly && !isARepostOfSomeoneElse(p)) {
      return false
    }
    if (uris.has(p.post.uri)) {
      return false
    }
    uris.add(p.post.uri)
    return true
  })

  // sort by index time
  posts.sort((a, b) => {
    return (
      Number(new Date(b.post.indexedAt)) - Number(new Date(a.post.indexedAt))
    )
  })

  return posts
}

function isARepostOfSomeoneElse(post: AppBskyFeedFeedViewPost.Main): boolean {
  return (
    post.reason?.$type === 'app.bsky.feed.feedViewPost#reasonRepost' &&
    post.post.author.did !== (post.reason as ReasonRepost).by.did
  )
}

function getCombinedCursors(responses: GetAuthorFeed.Response[]) {
  let hasCursor = false
  const cursors = responses.map(r => {
    if (r.data.cursor) {
      hasCursor = true
      return r.data.cursor
    }
    return ''
  })
  if (!hasCursor) {
    return undefined
  }
  const combinedCursors = cursors.join(',')
  return combinedCursors
}

function isCombinedCursor(cursor: string) {
  return cursor.includes(',')
}

export {
  getMultipleAuthorsPosts,
  mergePosts,
  getCombinedCursors,
  isCombinedCursor,
}