about summary refs log tree commit diff
path: root/src/lib/api/build-suggested-posts.ts
diff options
context:
space:
mode:
authorAnsh <anshnanda10@gmail.com>2023-03-02 10:21:33 -0800
committerGitHub <noreply@github.com>2023-03-02 12:21:33 -0600
commitbd9386d81c258d3d3f43666d3e25328f68428689 (patch)
tree8008c5dcfc41f85aac24abac0f6fec08dea6296f /src/lib/api/build-suggested-posts.ts
parent9b46b2e6a9a8e4e9254fa9031b2eb44a672e287f (diff)
downloadvoidsky-bd9386d81c258d3d3f43666d3e25328f68428689.tar.zst
New onboarding (#241)
* delete old onboarding files and code

* add custom FollowButton component to Post, FeedItem, & ProfileCard

* move building suggested feed into helper lib

* show suggested posts/feed if follower list is empty

* Update tsconfig.json

* add pagination to getting new onboarding

* remove unnecessary console log

* fix naming, add better null check for combinedCursor

* In locally-combined feeds, correctly produce an undefined cursor when out of data

* Minor refactors of the suggested posts lib functions

* Show 'follow button' style of post meta in certain conditions only

* Only show follow btn in posts on the main feed and the discovery feed

* Add a welcome notice to the home feed

* Tune the timing of when the welcome banner shows or hides

* Make the follow button an observer (closes #244)

* Update postmeta to keep the follow btn after press until next render

* A couple of fixes that ensure consistent welcome screen

* Fix lint

* Rework the welcome banner

* Fix cache invalidation of follows model on user switch

* Show welcome banner while loading

* Update the home onboarding feed to get top posts from hardcode recommends

* Drop unused helper function

* Update happy path tests

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Diffstat (limited to 'src/lib/api/build-suggested-posts.ts')
-rw-r--r--src/lib/api/build-suggested-posts.ts120
1 files changed, 120 insertions, 0 deletions
diff --git a/src/lib/api/build-suggested-posts.ts b/src/lib/api/build-suggested-posts.ts
new file mode 100644
index 000000000..6250f4a9c
--- /dev/null
+++ b/src/lib/api/build-suggested-posts.ts
@@ -0,0 +1,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,
+}