diff options
Diffstat (limited to 'src/lib/api')
-rw-r--r-- | src/lib/api/feed/custom.ts | 27 | ||||
-rw-r--r-- | src/lib/api/feed/home.ts | 89 | ||||
-rw-r--r-- | src/lib/api/feed/merge.ts | 19 | ||||
-rw-r--r-- | src/lib/api/search.ts | 69 |
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 -} |