about summary refs log tree commit diff
path: root/src/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/api')
-rw-r--r--src/lib/api/feed/custom.ts27
-rw-r--r--src/lib/api/feed/home.ts89
-rw-r--r--src/lib/api/feed/merge.ts19
-rw-r--r--src/lib/api/search.ts69
4 files changed, 119 insertions, 85 deletions
diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts
index 94cbff130..41c5367e5 100644
--- a/src/lib/api/feed/custom.ts
+++ b/src/lib/api/feed/custom.ts
@@ -4,15 +4,20 @@ import {
 } from '@atproto/api'
 import {FeedAPI, FeedAPIResponse} from './types'
 import {getAgent} from '#/state/session'
+import {getContentLanguages} from '#/state/preferences/languages'
 
 export class CustomFeedAPI implements FeedAPI {
   constructor(public params: GetCustomFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await getAgent().app.bsky.feed.getFeed({
-      ...this.params,
-      limit: 1,
-    })
+    const contentLangs = getContentLanguages().join(',')
+    const res = await getAgent().app.bsky.feed.getFeed(
+      {
+        ...this.params,
+        limit: 1,
+      },
+      {headers: {'Accept-Language': contentLangs}},
+    )
     return res.data.feed[0]
   }
 
@@ -23,11 +28,15 @@ export class CustomFeedAPI implements FeedAPI {
     cursor: string | undefined
     limit: number
   }): Promise<FeedAPIResponse> {
-    const res = await getAgent().app.bsky.feed.getFeed({
-      ...this.params,
-      cursor,
-      limit,
-    })
+    const contentLangs = getContentLanguages().join(',')
+    const res = await getAgent().app.bsky.feed.getFeed(
+      {
+        ...this.params,
+        cursor,
+        limit,
+      },
+      {headers: {'Accept-Language': contentLangs}},
+    )
     if (res.success) {
       // NOTE
       // some custom feeds fail to enforce the pagination limit
diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts
new file mode 100644
index 000000000..436a66d07
--- /dev/null
+++ b/src/lib/api/feed/home.ts
@@ -0,0 +1,89 @@
+import {AppBskyFeedDefs} from '@atproto/api'
+import {FeedAPI, FeedAPIResponse} from './types'
+import {FollowingFeedAPI} from './following'
+import {CustomFeedAPI} from './custom'
+import {PROD_DEFAULT_FEED} from '#/lib/constants'
+
+// HACK
+// the feed API does not include any facilities for passing down
+// non-post elements. adding that is a bit of a heavy lift, and we
+// have just one temporary usecase for it: flagging when the home feed
+// falls back to discover.
+// we use this fallback marker post to drive this instead. see Feed.tsx
+// for the usage.
+// -prf
+export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = {
+  post: {
+    uri: 'fallback-marker-post',
+    cid: 'fake',
+    record: {},
+    author: {
+      did: 'did:fake',
+      handle: 'fake.com',
+    },
+    indexedAt: new Date().toISOString(),
+  },
+}
+
+export class HomeFeedAPI implements FeedAPI {
+  following: FollowingFeedAPI
+  discover: CustomFeedAPI
+  usingDiscover = false
+  itemCursor = 0
+
+  constructor() {
+    this.following = new FollowingFeedAPI()
+    this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')})
+  }
+
+  reset() {
+    this.following = new FollowingFeedAPI()
+    this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')})
+    this.usingDiscover = false
+    this.itemCursor = 0
+  }
+
+  async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
+    if (this.usingDiscover) {
+      return this.discover.peekLatest()
+    }
+    return this.following.peekLatest()
+  }
+
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    if (!cursor) {
+      this.reset()
+    }
+
+    let returnCursor
+    let posts: AppBskyFeedDefs.FeedViewPost[] = []
+
+    if (!this.usingDiscover) {
+      const res = await this.following.fetch({cursor, limit})
+      returnCursor = res.cursor
+      posts = posts.concat(res.feed)
+      if (!returnCursor) {
+        cursor = ''
+        posts.push(FALLBACK_MARKER_POST)
+        this.usingDiscover = true
+      }
+    }
+
+    if (this.usingDiscover) {
+      const res = await this.discover.fetch({cursor, limit})
+      returnCursor = res.cursor
+      posts = posts.concat(res.feed)
+    }
+
+    return {
+      cursor: returnCursor,
+      feed: posts,
+    }
+  }
+}
diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts
index a4391afb2..28bf143cb 100644
--- a/src/lib/api/feed/merge.ts
+++ b/src/lib/api/feed/merge.ts
@@ -8,6 +8,7 @@ import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
 import {FeedParams} from '#/state/queries/post-feed'
 import {FeedTunerFn} from '../feed-manip'
 import {getAgent} from '#/state/session'
+import {getContentLanguages} from '#/state/preferences/languages'
 
 const REQUEST_WAIT_MS = 500 // 500ms
 const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
@@ -25,7 +26,7 @@ export class MergeFeedAPI implements FeedAPI {
 
   reset() {
     this.following = new MergeFeedSource_Following(this.feedTuners)
-    this.customFeeds = [] // just empty the array, they will be captured in _fetchNext()
+    this.customFeeds = []
     this.feedCursor = 0
     this.itemCursor = 0
     this.sampleCursor = 0
@@ -98,7 +99,7 @@ export class MergeFeedAPI implements FeedAPI {
     }
 
     return {
-      cursor: posts.length ? String(this.itemCursor) : undefined,
+      cursor: String(this.itemCursor),
       feed: posts,
     }
   }
@@ -231,11 +232,15 @@ class MergeFeedSource_Custom extends MergeFeedSource {
     limit: number,
   ): Promise<AppBskyFeedGetTimeline.Response> {
     try {
-      const res = await getAgent().app.bsky.feed.getFeed({
-        cursor,
-        limit,
-        feed: this.feedUri,
-      })
+      const contentLangs = getContentLanguages().join(',')
+      const res = await getAgent().app.bsky.feed.getFeed(
+        {
+          cursor,
+          limit,
+          feed: this.feedUri,
+        },
+        {headers: {'Accept-Language': contentLangs}},
+      )
       // NOTE
       // some custom feeds fail to enforce the pagination limit
       // so we manually truncate here
diff --git a/src/lib/api/search.ts b/src/lib/api/search.ts
deleted file mode 100644
index dfe9b688b..000000000
--- a/src/lib/api/search.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * This is a temporary off-spec search endpoint
- * TODO removeme when we land this in proto!
- */
-import {AppBskyFeedPost} from '@atproto/api'
-
-const PROFILES_ENDPOINT = 'https://search.bsky.social/search/profiles'
-const POSTS_ENDPOINT = 'https://search.bsky.social/search/posts'
-
-export interface ProfileSearchItem {
-  $type: string
-  avatar: {
-    cid: string
-    mimeType: string
-  }
-  banner: {
-    cid: string
-    mimeType: string
-  }
-  description: string | undefined
-  displayName: string | undefined
-  did: string
-}
-
-export interface PostSearchItem {
-  tid: string
-  cid: string
-  user: {
-    did: string
-    handle: string
-  }
-  post: AppBskyFeedPost.Record
-}
-
-export async function searchProfiles(
-  query: string,
-): Promise<ProfileSearchItem[]> {
-  return await doFetch<ProfileSearchItem[]>(PROFILES_ENDPOINT, query)
-}
-
-export async function searchPosts(query: string): Promise<PostSearchItem[]> {
-  return await doFetch<PostSearchItem[]>(POSTS_ENDPOINT, query)
-}
-
-async function doFetch<T>(endpoint: string, query: string): Promise<T> {
-  const controller = new AbortController()
-  const to = setTimeout(() => controller.abort(), 15e3)
-
-  const uri = new URL(endpoint)
-  uri.searchParams.set('q', query)
-
-  const res = await fetch(String(uri), {
-    method: 'get',
-    headers: {
-      accept: 'application/json',
-    },
-    signal: controller.signal,
-  })
-
-  const resHeaders: Record<string, string> = {}
-  res.headers.forEach((value: string, key: string) => {
-    resHeaders[key] = value
-  })
-  let resBody = await res.json()
-
-  clearTimeout(to)
-
-  return resBody as unknown as T
-}