diff --git a/src/lib/api/feed/author.ts b/src/lib/api/feed/author.ts index 57db061b..85601d06 100644 --- a/src/lib/api/feed/author.ts +++ b/src/lib/api/feed/author.ts @@ -1,15 +1,28 @@ import { AppBskyFeedDefs, AppBskyFeedGetAuthorFeed as GetAuthorFeed, + BskyAgent, } from '@atproto/api' + import {FeedAPI, FeedAPIResponse} from './types' -import {getAgent} from '#/state/session' export class AuthorFeedAPI implements FeedAPI { - constructor(public params: GetAuthorFeed.QueryParams) {} + getAgent: () => BskyAgent + params: GetAuthorFeed.QueryParams + + constructor({ + getAgent, + feedParams, + }: { + getAgent: () => BskyAgent + feedParams: GetAuthorFeed.QueryParams + }) { + this.getAgent = getAgent + this.params = feedParams + } async peekLatest(): Promise { - const res = await getAgent().getAuthorFeed({ + const res = await this.getAgent().getAuthorFeed({ ...this.params, limit: 1, }) @@ -23,7 +36,7 @@ export class AuthorFeedAPI implements FeedAPI { cursor: string | undefined limit: number }): Promise { - const res = await getAgent().getAuthorFeed({ + const res = await this.getAgent().getAuthorFeed({ ...this.params, cursor, limit, diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts index bd30d58a..75182c41 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -2,18 +2,30 @@ import { AppBskyFeedDefs, AppBskyFeedGetFeed as GetCustomFeed, AtpAgent, + BskyAgent, } from '@atproto/api' import {getContentLanguages} from '#/state/preferences/languages' -import {getAgent} from '#/state/session' import {FeedAPI, FeedAPIResponse} from './types' export class CustomFeedAPI implements FeedAPI { - constructor(public params: GetCustomFeed.QueryParams) {} + getAgent: () => BskyAgent + params: GetCustomFeed.QueryParams + + constructor({ + getAgent, + feedParams, + }: { + getAgent: () => BskyAgent + feedParams: GetCustomFeed.QueryParams + }) { + this.getAgent = getAgent + this.params = feedParams + } async peekLatest(): Promise { const contentLangs = getContentLanguages().join(',') - const res = await getAgent().app.bsky.feed.getFeed( + const res = await this.getAgent().app.bsky.feed.getFeed( { ...this.params, limit: 1, @@ -31,15 +43,19 @@ export class CustomFeedAPI implements FeedAPI { limit: number }): Promise { const contentLangs = getContentLanguages().join(',') - const agent = getAgent() + const agent = this.getAgent() const res = agent.session - ? await getAgent().app.bsky.feed.getFeed( + ? await this.getAgent().app.bsky.feed.getFeed( { ...this.params, cursor, limit, }, - {headers: {'Accept-Language': contentLangs}}, + { + headers: { + 'Accept-Language': contentLangs, + }, + }, ) : await loggedOutFetch({...this.params, cursor, limit}) if (res.success) { diff --git a/src/lib/api/feed/following.ts b/src/lib/api/feed/following.ts index 24389b5e..36c37655 100644 --- a/src/lib/api/feed/following.ts +++ b/src/lib/api/feed/following.ts @@ -1,12 +1,16 @@ -import {AppBskyFeedDefs} from '@atproto/api' +import {AppBskyFeedDefs, BskyAgent} from '@atproto/api' + import {FeedAPI, FeedAPIResponse} from './types' -import {getAgent} from '#/state/session' export class FollowingFeedAPI implements FeedAPI { - constructor() {} + getAgent: () => BskyAgent + + constructor({getAgent}: {getAgent: () => BskyAgent}) { + this.getAgent = getAgent + } async peekLatest(): Promise { - const res = await getAgent().getTimeline({ + const res = await this.getAgent().getTimeline({ limit: 1, }) return res.data.feed[0] @@ -19,7 +23,7 @@ export class FollowingFeedAPI implements FeedAPI { cursor: string | undefined limit: number }): Promise { - const res = await getAgent().getTimeline({ + const res = await this.getAgent().getTimeline({ cursor, limit, }) diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index 436a66d0..4a530834 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -1,8 +1,9 @@ -import {AppBskyFeedDefs} from '@atproto/api' -import {FeedAPI, FeedAPIResponse} from './types' -import {FollowingFeedAPI} from './following' -import {CustomFeedAPI} from './custom' +import {AppBskyFeedDefs, BskyAgent} from '@atproto/api' + import {PROD_DEFAULT_FEED} from '#/lib/constants' +import {CustomFeedAPI} from './custom' +import {FollowingFeedAPI} from './following' +import {FeedAPI, FeedAPIResponse} from './types' // HACK // the feed API does not include any facilities for passing down @@ -26,19 +27,27 @@ export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = { } export class HomeFeedAPI implements FeedAPI { + getAgent: () => BskyAgent following: FollowingFeedAPI discover: CustomFeedAPI usingDiscover = false itemCursor = 0 - constructor() { - this.following = new FollowingFeedAPI() - this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')}) + constructor({getAgent}: {getAgent: () => BskyAgent}) { + this.getAgent = getAgent + this.following = new FollowingFeedAPI({getAgent}) + this.discover = new CustomFeedAPI({ + getAgent, + feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, + }) } reset() { - this.following = new FollowingFeedAPI() - this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')}) + this.following = new FollowingFeedAPI({getAgent: this.getAgent}) + this.discover = new CustomFeedAPI({ + getAgent: this.getAgent, + feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, + }) this.usingDiscover = false this.itemCursor = 0 } diff --git a/src/lib/api/feed/likes.ts b/src/lib/api/feed/likes.ts index 2b0afdf1..1729ee05 100644 --- a/src/lib/api/feed/likes.ts +++ b/src/lib/api/feed/likes.ts @@ -1,15 +1,28 @@ import { AppBskyFeedDefs, AppBskyFeedGetActorLikes as GetActorLikes, + BskyAgent, } from '@atproto/api' + import {FeedAPI, FeedAPIResponse} from './types' -import {getAgent} from '#/state/session' export class LikesFeedAPI implements FeedAPI { - constructor(public params: GetActorLikes.QueryParams) {} + getAgent: () => BskyAgent + params: GetActorLikes.QueryParams + + constructor({ + getAgent, + feedParams, + }: { + getAgent: () => BskyAgent + feedParams: GetActorLikes.QueryParams + }) { + this.getAgent = getAgent + this.params = feedParams + } async peekLatest(): Promise { - const res = await getAgent().getActorLikes({ + const res = await this.getAgent().getActorLikes({ ...this.params, limit: 1, }) @@ -23,7 +36,7 @@ export class LikesFeedAPI implements FeedAPI { cursor: string | undefined limit: number }): Promise { - const res = await getAgent().getActorLikes({ + const res = await this.getAgent().getActorLikes({ ...this.params, cursor, limit, diff --git a/src/lib/api/feed/list.ts b/src/lib/api/feed/list.ts index 19f2ff17..004685b9 100644 --- a/src/lib/api/feed/list.ts +++ b/src/lib/api/feed/list.ts @@ -1,15 +1,28 @@ import { AppBskyFeedDefs, AppBskyFeedGetListFeed as GetListFeed, + BskyAgent, } from '@atproto/api' + import {FeedAPI, FeedAPIResponse} from './types' -import {getAgent} from '#/state/session' export class ListFeedAPI implements FeedAPI { - constructor(public params: GetListFeed.QueryParams) {} + getAgent: () => BskyAgent + params: GetListFeed.QueryParams + + constructor({ + getAgent, + feedParams, + }: { + getAgent: () => BskyAgent + feedParams: GetListFeed.QueryParams + }) { + this.getAgent = getAgent + this.params = feedParams + } async peekLatest(): Promise { - const res = await getAgent().app.bsky.feed.getListFeed({ + const res = await this.getAgent().app.bsky.feed.getListFeed({ ...this.params, limit: 1, }) @@ -23,7 +36,7 @@ export class ListFeedAPI implements FeedAPI { cursor: string | undefined limit: number }): Promise { - const res = await getAgent().app.bsky.feed.getListFeed({ + const res = await this.getAgent().app.bsky.feed.getListFeed({ ...this.params, cursor, limit, diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index 28bf143c..c85de030 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -1,31 +1,51 @@ -import {AppBskyFeedDefs, AppBskyFeedGetTimeline} from '@atproto/api' +import {AppBskyFeedDefs, AppBskyFeedGetTimeline, BskyAgent} from '@atproto/api' import shuffle from 'lodash.shuffle' -import {timeout} from 'lib/async/timeout' + +import {getContentLanguages} from '#/state/preferences/languages' +import {FeedParams} from '#/state/queries/post-feed' import {bundleAsync} from 'lib/async/bundle' +import {timeout} from 'lib/async/timeout' import {feedUriToHref} from 'lib/strings/url-helpers' import {FeedTuner} from '../feed-manip' -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' +import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types' const REQUEST_WAIT_MS = 500 // 500ms const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours export class MergeFeedAPI implements FeedAPI { + getAgent: () => BskyAgent + params: FeedParams + feedTuners: FeedTunerFn[] following: MergeFeedSource_Following customFeeds: MergeFeedSource_Custom[] = [] feedCursor = 0 itemCursor = 0 sampleCursor = 0 - constructor(public params: FeedParams, public feedTuners: FeedTunerFn[]) { - this.following = new MergeFeedSource_Following(this.feedTuners) + constructor({ + getAgent, + feedParams, + feedTuners, + }: { + getAgent: () => BskyAgent + feedParams: FeedParams + feedTuners: FeedTunerFn[] + }) { + this.getAgent = getAgent + this.params = feedParams + this.feedTuners = feedTuners + this.following = new MergeFeedSource_Following({ + getAgent: this.getAgent, + feedTuners: this.feedTuners, + }) } reset() { - this.following = new MergeFeedSource_Following(this.feedTuners) + this.following = new MergeFeedSource_Following({ + getAgent: this.getAgent, + feedTuners: this.feedTuners, + }) this.customFeeds = [] this.feedCursor = 0 this.itemCursor = 0 @@ -33,7 +53,12 @@ export class MergeFeedAPI implements FeedAPI { if (this.params.mergeFeedSources) { this.customFeeds = shuffle( this.params.mergeFeedSources.map( - feedUri => new MergeFeedSource_Custom(feedUri, this.feedTuners), + feedUri => + new MergeFeedSource_Custom({ + getAgent: this.getAgent, + feedUri, + feedTuners: this.feedTuners, + }), ), ) } else { @@ -42,7 +67,7 @@ export class MergeFeedAPI implements FeedAPI { } async peekLatest(): Promise { - const res = await getAgent().getTimeline({ + const res = await this.getAgent().getTimeline({ limit: 1, }) return res.data.feed[0] @@ -136,12 +161,23 @@ export class MergeFeedAPI implements FeedAPI { } class MergeFeedSource { + getAgent: () => BskyAgent + feedTuners: FeedTunerFn[] sourceInfo: ReasonFeedSource | undefined cursor: string | undefined = undefined queue: AppBskyFeedDefs.FeedViewPost[] = [] hasMore = true - constructor(public feedTuners: FeedTunerFn[]) {} + constructor({ + getAgent, + feedTuners, + }: { + getAgent: () => BskyAgent + feedTuners: FeedTunerFn[] + }) { + this.getAgent = getAgent + this.feedTuners = feedTuners + } get numReady() { return this.queue.length @@ -203,7 +239,7 @@ class MergeFeedSource_Following extends MergeFeedSource { cursor: string | undefined, limit: number, ): Promise { - const res = await getAgent().getTimeline({cursor, limit}) + const res = await this.getAgent().getTimeline({cursor, limit}) // run the tuner pre-emptively to ensure better mixing const slices = this.tuner.tune(res.data.feed, { dryRun: false, @@ -215,10 +251,25 @@ class MergeFeedSource_Following extends MergeFeedSource { } class MergeFeedSource_Custom extends MergeFeedSource { + getAgent: () => BskyAgent minDate: Date + feedUri: string - constructor(public feedUri: string, public feedTuners: FeedTunerFn[]) { - super(feedTuners) + constructor({ + getAgent, + feedUri, + feedTuners, + }: { + getAgent: () => BskyAgent + feedUri: string + feedTuners: FeedTunerFn[] + }) { + super({ + getAgent, + feedTuners, + }) + this.getAgent = getAgent + this.feedUri = feedUri this.sourceInfo = { $type: 'reasonFeedSource', uri: feedUri, @@ -233,13 +284,17 @@ class MergeFeedSource_Custom extends MergeFeedSource { ): Promise { try { const contentLangs = getContentLanguages().join(',') - const res = await getAgent().app.bsky.feed.getFeed( + const res = await this.getAgent().app.bsky.feed.getFeed( { cursor, limit, feed: this.feedUri, }, - {headers: {'Accept-Language': contentLangs}}, + { + headers: { + 'Accept-Language': contentLangs, + }, + }, ) // NOTE // some custom feeds fail to enforce the pagination limit diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 3453a776..2d5c0d4b 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -135,11 +135,14 @@ export function usePostFeedQuery( queryKey: RQKEY(feedDesc, params), async queryFn({pageParam}: {pageParam: RQPageParam}) { logger.debug('usePostFeedQuery', {feedDesc, cursor: pageParam?.cursor}) - const {api, cursor} = pageParam ? pageParam : { - api: createApi(feedDesc, params || {}, feedTuners), + api: createApi({ + feedDesc, + feedParams: params || {}, + feedTuners, + }), cursor: undefined, } @@ -365,34 +368,45 @@ export async function pollLatest(page: FeedPage | undefined) { return false } -function createApi( - feedDesc: FeedDescriptor, - params: FeedParams, - feedTuners: FeedTunerFn[], -) { +function createApi({ + feedDesc, + feedParams, + feedTuners, +}: { + feedDesc: FeedDescriptor + feedParams: FeedParams + feedTuners: FeedTunerFn[] +}) { if (feedDesc === 'home') { - if (params.mergeFeedEnabled) { - return new MergeFeedAPI(params, feedTuners) + if (feedParams.mergeFeedEnabled) { + return new MergeFeedAPI({ + getAgent, + feedParams, + feedTuners, + }) } else { - return new HomeFeedAPI() + return new HomeFeedAPI({getAgent}) } } else if (feedDesc === 'following') { - return new FollowingFeedAPI() + return new FollowingFeedAPI({getAgent}) } else if (feedDesc.startsWith('author')) { const [_, actor, filter] = feedDesc.split('|') - return new AuthorFeedAPI({actor, filter}) + return new AuthorFeedAPI({getAgent, feedParams: {actor, filter}}) } else if (feedDesc.startsWith('likes')) { const [_, actor] = feedDesc.split('|') - return new LikesFeedAPI({actor}) + return new LikesFeedAPI({getAgent, feedParams: {actor}}) } else if (feedDesc.startsWith('feedgen')) { const [_, feed] = feedDesc.split('|') - return new CustomFeedAPI({feed}) + return new CustomFeedAPI({ + getAgent, + feedParams: {feed}, + }) } else if (feedDesc.startsWith('list')) { const [_, list] = feedDesc.split('|') - return new ListFeedAPI({list}) + return new ListFeedAPI({getAgent, feedParams: {list}}) } else { // shouldnt happen - return new FollowingFeedAPI() + return new FollowingFeedAPI({getAgent}) } }