about summary refs log tree commit diff
path: root/src/lib/api/feed
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/api/feed')
-rw-r--r--src/lib/api/feed/author.ts28
-rw-r--r--src/lib/api/feed/custom.ts28
-rw-r--r--src/lib/api/feed/following.ts25
-rw-r--r--src/lib/api/feed/likes.ts28
-rw-r--r--src/lib/api/feed/list.ts28
-rw-r--r--src/lib/api/feed/merge.ts82
-rw-r--r--src/lib/api/feed/types.ts21
7 files changed, 120 insertions, 120 deletions
diff --git a/src/lib/api/feed/author.ts b/src/lib/api/feed/author.ts
index ec8795e1a..92df84f8b 100644
--- a/src/lib/api/feed/author.ts
+++ b/src/lib/api/feed/author.ts
@@ -2,37 +2,33 @@ import {
   AppBskyFeedDefs,
   AppBskyFeedGetAuthorFeed as GetAuthorFeed,
 } from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class AuthorFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetAuthorFeed.QueryParams,
-  ) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor(public params: GetAuthorFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.getAuthorFeed({
+    const res = await getAgent().getAuthorFeed({
       ...this.params,
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.getAuthorFeed({
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().getAuthorFeed({
       ...this.params,
-      cursor: this.cursor,
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       return {
         cursor: res.data.cursor,
         feed: this._filter(res.data.feed),
diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts
index d05d5acd6..47ffc65ed 100644
--- a/src/lib/api/feed/custom.ts
+++ b/src/lib/api/feed/custom.ts
@@ -2,37 +2,33 @@ import {
   AppBskyFeedDefs,
   AppBskyFeedGetFeed as GetCustomFeed,
 } from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class CustomFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetCustomFeed.QueryParams,
-  ) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor(public params: GetCustomFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.app.bsky.feed.getFeed({
+    const res = await getAgent().app.bsky.feed.getFeed({
       ...this.params,
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.app.bsky.feed.getFeed({
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().app.bsky.feed.getFeed({
       ...this.params,
-      cursor: this.cursor,
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       // NOTE
       // some custom feeds fail to enforce the pagination limit
       // so we manually truncate here
diff --git a/src/lib/api/feed/following.ts b/src/lib/api/feed/following.ts
index f14807a57..24389b5ed 100644
--- a/src/lib/api/feed/following.ts
+++ b/src/lib/api/feed/following.ts
@@ -1,30 +1,29 @@
 import {AppBskyFeedDefs} from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class FollowingFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(public rootStore: RootStoreModel) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor() {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.getTimeline({
+    const res = await getAgent().getTimeline({
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.getTimeline({
-      cursor: this.cursor,
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().getTimeline({
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       return {
         cursor: res.data.cursor,
         feed: res.data.feed,
diff --git a/src/lib/api/feed/likes.ts b/src/lib/api/feed/likes.ts
index e9bb14b0b..2b0afdf11 100644
--- a/src/lib/api/feed/likes.ts
+++ b/src/lib/api/feed/likes.ts
@@ -2,37 +2,33 @@ import {
   AppBskyFeedDefs,
   AppBskyFeedGetActorLikes as GetActorLikes,
 } from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class LikesFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetActorLikes.QueryParams,
-  ) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor(public params: GetActorLikes.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.getActorLikes({
+    const res = await getAgent().getActorLikes({
       ...this.params,
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.getActorLikes({
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().getActorLikes({
       ...this.params,
-      cursor: this.cursor,
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       return {
         cursor: res.data.cursor,
         feed: res.data.feed,
diff --git a/src/lib/api/feed/list.ts b/src/lib/api/feed/list.ts
index e58494675..19f2ff177 100644
--- a/src/lib/api/feed/list.ts
+++ b/src/lib/api/feed/list.ts
@@ -2,37 +2,33 @@ import {
   AppBskyFeedDefs,
   AppBskyFeedGetListFeed as GetListFeed,
 } from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class ListFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetListFeed.QueryParams,
-  ) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor(public params: GetListFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.app.bsky.feed.getListFeed({
+    const res = await getAgent().app.bsky.feed.getListFeed({
       ...this.params,
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.app.bsky.feed.getListFeed({
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().app.bsky.feed.getListFeed({
       ...this.params,
-      cursor: this.cursor,
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       return {
         cursor: res.data.cursor,
         feed: res.data.feed,
diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts
index e0fbcecd8..11e963f0a 100644
--- a/src/lib/api/feed/merge.ts
+++ b/src/lib/api/feed/merge.ts
@@ -1,11 +1,13 @@
 import {AppBskyFeedDefs, AppBskyFeedGetTimeline} from '@atproto/api'
 import shuffle from 'lodash.shuffle'
-import {RootStoreModel} from 'state/index'
 import {timeout} from 'lib/async/timeout'
 import {bundleAsync} from 'lib/async/bundle'
 import {feedUriToHref} from 'lib/strings/url-helpers'
 import {FeedTuner} from '../feed-manip'
-import {FeedAPI, FeedAPIResponse, FeedSourceInfo} from './types'
+import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
+import {FeedParams} from '#/state/queries/post-feed'
+import {FeedTunerFn} from '../feed-manip'
+import {getAgent} from '#/state/session'
 
 const REQUEST_WAIT_MS = 500 // 500ms
 const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
@@ -17,28 +19,44 @@ export class MergeFeedAPI implements FeedAPI {
   itemCursor = 0
   sampleCursor = 0
 
-  constructor(public rootStore: RootStoreModel) {
-    this.following = new MergeFeedSource_Following(this.rootStore)
+  constructor(public params: FeedParams, public feedTuners: FeedTunerFn[]) {
+    this.following = new MergeFeedSource_Following(this.feedTuners)
   }
 
   reset() {
-    this.following = new MergeFeedSource_Following(this.rootStore)
+    this.following = new MergeFeedSource_Following(this.feedTuners)
     this.customFeeds = [] // just empty the array, they will be captured in _fetchNext()
     this.feedCursor = 0
     this.itemCursor = 0
     this.sampleCursor = 0
+    if (this.params.mergeFeedEnabled && this.params.mergeFeedSources) {
+      this.customFeeds = shuffle(
+        this.params.mergeFeedSources.map(
+          feedUri => new MergeFeedSource_Custom(feedUri, this.feedTuners),
+        ),
+      )
+    } else {
+      this.customFeeds = []
+    }
   }
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.getTimeline({
+    const res = await getAgent().getTimeline({
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    // we capture here to ensure the data has loaded
-    this._captureFeedsIfNeeded()
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    if (!cursor) {
+      this.reset()
+    }
 
     const promises = []
 
@@ -76,7 +94,7 @@ export class MergeFeedAPI implements FeedAPI {
     }
 
     return {
-      cursor: posts.length ? 'fake' : undefined,
+      cursor: posts.length ? String(this.itemCursor) : undefined,
       feed: posts,
     }
   }
@@ -107,28 +125,15 @@ export class MergeFeedAPI implements FeedAPI {
     // provide follow
     return this.following.take(1)
   }
-
-  _captureFeedsIfNeeded() {
-    if (!this.rootStore.preferences.homeFeed.lab_mergeFeedEnabled) {
-      return
-    }
-    if (this.customFeeds.length === 0) {
-      this.customFeeds = shuffle(
-        this.rootStore.preferences.savedFeeds.map(
-          feedUri => new MergeFeedSource_Custom(this.rootStore, feedUri),
-        ),
-      )
-    }
-  }
 }
 
 class MergeFeedSource {
-  sourceInfo: FeedSourceInfo | undefined
+  sourceInfo: ReasonFeedSource | undefined
   cursor: string | undefined = undefined
   queue: AppBskyFeedDefs.FeedViewPost[] = []
   hasMore = true
 
-  constructor(public rootStore: RootStoreModel) {}
+  constructor(public feedTuners: FeedTunerFn[]) {}
 
   get numReady() {
     return this.queue.length
@@ -175,7 +180,7 @@ class MergeFeedSource {
 }
 
 class MergeFeedSource_Following extends MergeFeedSource {
-  tuner = new FeedTuner()
+  tuner = new FeedTuner(this.feedTuners)
 
   reset() {
     super.reset()
@@ -190,16 +195,12 @@ class MergeFeedSource_Following extends MergeFeedSource {
     cursor: string | undefined,
     limit: number,
   ): Promise<AppBskyFeedGetTimeline.Response> {
-    const res = await this.rootStore.agent.getTimeline({cursor, limit})
+    const res = await getAgent().getTimeline({cursor, limit})
     // run the tuner pre-emptively to ensure better mixing
-    const slices = this.tuner.tune(
-      res.data.feed,
-      this.rootStore.preferences.getFeedTuners('home'),
-      {
-        dryRun: false,
-        maintainOrder: true,
-      },
-    )
+    const slices = this.tuner.tune(res.data.feed, {
+      dryRun: false,
+      maintainOrder: true,
+    })
     res.data.feed = slices.map(slice => slice.rootItem)
     return res
   }
@@ -208,15 +209,16 @@ class MergeFeedSource_Following extends MergeFeedSource {
 class MergeFeedSource_Custom extends MergeFeedSource {
   minDate: Date
 
-  constructor(public rootStore: RootStoreModel, public feedUri: string) {
-    super(rootStore)
+  constructor(public feedUri: string, public feedTuners: FeedTunerFn[]) {
+    super(feedTuners)
     this.sourceInfo = {
+      $type: 'reasonFeedSource',
       displayName: feedUri.split('/').pop() || '',
       uri: feedUriToHref(feedUri),
     }
     this.minDate = new Date(Date.now() - POST_AGE_CUTOFF)
-    this.rootStore.agent.app.bsky.feed
-      .getFeedGenerator({
+    getAgent()
+      .app.bsky.feed.getFeedGenerator({
         feed: feedUri,
       })
       .then(
@@ -234,7 +236,7 @@ class MergeFeedSource_Custom extends MergeFeedSource {
     limit: number,
   ): Promise<AppBskyFeedGetTimeline.Response> {
     try {
-      const res = await this.rootStore.agent.app.bsky.feed.getFeed({
+      const res = await getAgent().app.bsky.feed.getFeed({
         cursor,
         limit,
         feed: this.feedUri,
diff --git a/src/lib/api/feed/types.ts b/src/lib/api/feed/types.ts
index 006344334..5d2a90c1d 100644
--- a/src/lib/api/feed/types.ts
+++ b/src/lib/api/feed/types.ts
@@ -6,12 +6,27 @@ export interface FeedAPIResponse {
 }
 
 export interface FeedAPI {
-  reset(): void
   peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost>
-  fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse>
+  fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse>
 }
 
-export interface FeedSourceInfo {
+export interface ReasonFeedSource {
+  $type: 'reasonFeedSource'
   uri: string
   displayName: string
 }
+
+export function isReasonFeedSource(v: unknown): v is ReasonFeedSource {
+  return (
+    !!v &&
+    typeof v === 'object' &&
+    '$type' in v &&
+    v.$type === 'reasonFeedSource'
+  )
+}