From cc977c8a94f7dc6b0986b3b6a4b42423105354cb Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 24 Apr 2024 18:12:38 -0500 Subject: [PATCH 1/6] WIP --- src/lib/api/feed/custom.ts | 17 ++++++++++++-- src/lib/api/feed/home.ts | 22 +++++++++++++----- src/lib/api/feed/merge.ts | 34 ++++++++++++++++++++------- src/lib/api/feed/utils.ts | 15 ++++++++++++ src/state/queries/post-feed.ts | 42 ++++++++++++++++++++++++---------- 5 files changed, 102 insertions(+), 28 deletions(-) create mode 100644 src/lib/api/feed/utils.ts diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts index bd30d58acb..b57c03e43d 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -5,11 +5,16 @@ import { } from '@atproto/api' import {getContentLanguages} from '#/state/preferences/languages' +import {UsePreferencesQueryResponse} from '#/state/queries/preferences' import {getAgent} from '#/state/session' import {FeedAPI, FeedAPIResponse} from './types' +import {aggregateUserInterests, isBlueskyOwnedFeed} from './utils' export class CustomFeedAPI implements FeedAPI { - constructor(public params: GetCustomFeed.QueryParams) {} + constructor( + public params: GetCustomFeed.QueryParams, + public preferences?: UsePreferencesQueryResponse, + ) {} async peekLatest(): Promise { const contentLangs = getContentLanguages().join(',') @@ -32,6 +37,9 @@ export class CustomFeedAPI implements FeedAPI { }): Promise { const contentLangs = getContentLanguages().join(',') const agent = getAgent() + const isBlueskyOwned = isBlueskyOwnedFeed(this.params.feed) + const interests = aggregateUserInterests(this.preferences) + const res = agent.session ? await getAgent().app.bsky.feed.getFeed( { @@ -39,7 +47,12 @@ export class CustomFeedAPI implements FeedAPI { cursor, limit, }, - {headers: {'Accept-Language': contentLangs}}, + { + headers: { + 'X-Bsky-Topics': isBlueskyOwned ? interests : '', + 'Accept-Language': contentLangs, + }, + }, ) : await loggedOutFetch({...this.params, cursor, limit}) if (res.success) { diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index 436a66d076..e20ced97a4 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -1,8 +1,10 @@ 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' +import {UsePreferencesQueryResponse} from '#/state/queries/preferences' +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 @@ -30,15 +32,23 @@ export class HomeFeedAPI implements FeedAPI { discover: CustomFeedAPI usingDiscover = false itemCursor = 0 + preferences?: UsePreferencesQueryResponse - constructor() { + constructor({preferences}: {preferences?: UsePreferencesQueryResponse}) { + this.preferences = preferences this.following = new FollowingFeedAPI() - this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')}) + this.discover = new CustomFeedAPI( + {feed: PROD_DEFAULT_FEED('whats-hot')}, + preferences, + ) } reset() { this.following = new FollowingFeedAPI() - this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')}) + this.discover = new CustomFeedAPI( + {feed: PROD_DEFAULT_FEED('whats-hot')}, + this.preferences, + ) this.usingDiscover = false this.itemCursor = 0 } diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index 28bf143cbb..c1a6d732ec 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -1,14 +1,17 @@ import {AppBskyFeedDefs, AppBskyFeedGetTimeline} 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 {UsePreferencesQueryResponse} from '#/state/queries/preferences' +import {getAgent} from '#/state/session' 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' +import {aggregateUserInterests, isBlueskyOwnedFeed} from './utils' const REQUEST_WAIT_MS = 500 // 500ms const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours @@ -20,7 +23,11 @@ export class MergeFeedAPI implements FeedAPI { itemCursor = 0 sampleCursor = 0 - constructor(public params: FeedParams, public feedTuners: FeedTunerFn[]) { + constructor( + public params: FeedParams, + public feedTuners: FeedTunerFn[], + public preferences: UsePreferencesQueryResponse, + ) { this.following = new MergeFeedSource_Following(this.feedTuners) } @@ -217,7 +224,11 @@ class MergeFeedSource_Following extends MergeFeedSource { class MergeFeedSource_Custom extends MergeFeedSource { minDate: Date - constructor(public feedUri: string, public feedTuners: FeedTunerFn[]) { + constructor( + public feedUri: string, + public feedTuners: FeedTunerFn[], + public preferences?: UsePreferencesQueryResponse, + ) { super(feedTuners) this.sourceInfo = { $type: 'reasonFeedSource', @@ -233,13 +244,20 @@ class MergeFeedSource_Custom extends MergeFeedSource { ): Promise { try { const contentLangs = getContentLanguages().join(',') + const isBlueskyOwned = isBlueskyOwnedFeed(this.feedUri) + const interests = aggregateUserInterests(this.preferences) const res = await getAgent().app.bsky.feed.getFeed( { cursor, limit, feed: this.feedUri, }, - {headers: {'Accept-Language': contentLangs}}, + { + headers: { + 'X-Bsky-Topics': isBlueskyOwned ? interests : '', + 'Accept-Language': contentLangs, + }, + }, ) // NOTE // some custom feeds fail to enforce the pagination limit diff --git a/src/lib/api/feed/utils.ts b/src/lib/api/feed/utils.ts new file mode 100644 index 0000000000..a42a592f1a --- /dev/null +++ b/src/lib/api/feed/utils.ts @@ -0,0 +1,15 @@ +import {AtUri} from '@atproto/api' + +import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' +import {UsePreferencesQueryResponse} from '#/state/queries/preferences' + +export function aggregateUserInterests( + preferences?: UsePreferencesQueryResponse, +) { + return preferences?.interests?.tags?.join(',') || '' +} + +export function isBlueskyOwnedFeed(feedUri: string) { + const uri = new AtUri(feedUri) + return BSKY_FEED_OWNER_DIDS.includes(uri.host) +} diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 3453a77648..bdc611b6df 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -31,7 +31,11 @@ import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip' import {BSKY_FEED_OWNER_DIDS} from 'lib/constants' import {KnownError} from '#/view/com/posts/FeedErrorMessage' import {useFeedTuners} from '../preferences/feed-tuners' -import {useModerationOpts} from './preferences' +import { + useModerationOpts, + usePreferencesQuery, + UsePreferencesQueryResponse, +} from './preferences' import {precacheFeedPostProfiles} from './profile' import {embedViewRecordToPostView, getEmbeddedPost} from './util' @@ -104,7 +108,9 @@ export function usePostFeedQuery( const queryClient = useQueryClient() const feedTuners = useFeedTuners(feedDesc) const moderationOpts = useModerationOpts() - const enabled = opts?.enabled !== false && Boolean(moderationOpts) + const {data: preferences} = usePreferencesQuery() + const enabled = + opts?.enabled !== false && Boolean(moderationOpts) && Boolean(preferences) const lastRun = useRef<{ data: InfiniteData args: typeof selectArgs @@ -139,7 +145,12 @@ export function usePostFeedQuery( const {api, cursor} = pageParam ? pageParam : { - api: createApi(feedDesc, params || {}, feedTuners), + api: createApi({ + feedDesc, + feedParams: params || {}, + feedTuners, + preferences, + }), cursor: undefined, } @@ -365,16 +376,23 @@ export async function pollLatest(page: FeedPage | undefined) { return false } -function createApi( - feedDesc: FeedDescriptor, - params: FeedParams, - feedTuners: FeedTunerFn[], -) { +function createApi({ + feedDesc, + feedParams, + feedTuners, + preferences, +}: { + feedDesc: FeedDescriptor + feedParams: FeedParams + feedTuners: FeedTunerFn[] + preferences: UsePreferencesQueryResponse +}) { if (feedDesc === 'home') { - if (params.mergeFeedEnabled) { - return new MergeFeedAPI(params, feedTuners) + if (feedParams.mergeFeedEnabled) { + console.log('MERGE') + return new MergeFeedAPI(feedParams, feedTuners, preferences) } else { - return new HomeFeedAPI() + return new HomeFeedAPI({preferences}) } } else if (feedDesc === 'following') { return new FollowingFeedAPI() @@ -386,7 +404,7 @@ function createApi( return new LikesFeedAPI({actor}) } else if (feedDesc.startsWith('feedgen')) { const [_, feed] = feedDesc.split('|') - return new CustomFeedAPI({feed}) + return new CustomFeedAPI({feed}, preferences) } else if (feedDesc.startsWith('list')) { const [_, list] = feedDesc.split('|') return new ListFeedAPI({list}) From a01b5b785112b383d13a7c86d0792cf7360613fe Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 24 Apr 2024 18:21:06 -0500 Subject: [PATCH 2/6] Fix constructors --- src/lib/api/feed/custom.ts | 17 ++++++++++--- src/lib/api/feed/home.ts | 14 +++++------ src/lib/api/feed/merge.ts | 45 +++++++++++++++++++++++++--------- src/state/queries/post-feed.ts | 14 ++++++++--- 4 files changed, 64 insertions(+), 26 deletions(-) diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts index b57c03e43d..24693bfba8 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -11,10 +11,19 @@ import {FeedAPI, FeedAPIResponse} from './types' import {aggregateUserInterests, isBlueskyOwnedFeed} from './utils' export class CustomFeedAPI implements FeedAPI { - constructor( - public params: GetCustomFeed.QueryParams, - public preferences?: UsePreferencesQueryResponse, - ) {} + params: GetCustomFeed.QueryParams + preferences?: UsePreferencesQueryResponse + + constructor({ + feedParams, + preferences, + }: { + feedParams: GetCustomFeed.QueryParams + preferences?: UsePreferencesQueryResponse + }) { + this.params = feedParams + this.preferences = preferences + } async peekLatest(): Promise { const contentLangs = getContentLanguages().join(',') diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index e20ced97a4..f23058470d 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -37,18 +37,18 @@ export class HomeFeedAPI implements FeedAPI { constructor({preferences}: {preferences?: UsePreferencesQueryResponse}) { this.preferences = preferences this.following = new FollowingFeedAPI() - this.discover = new CustomFeedAPI( - {feed: PROD_DEFAULT_FEED('whats-hot')}, + this.discover = new CustomFeedAPI({ + feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, preferences, - ) + }) } reset() { this.following = new FollowingFeedAPI() - this.discover = new CustomFeedAPI( - {feed: PROD_DEFAULT_FEED('whats-hot')}, - this.preferences, - ) + this.discover = new CustomFeedAPI({ + feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, + preferences: this.preferences, + }) this.usingDiscover = false this.itemCursor = 0 } diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index c1a6d732ec..7d9037851e 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -17,17 +17,27 @@ const REQUEST_WAIT_MS = 500 // 500ms const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours export class MergeFeedAPI implements FeedAPI { + params: FeedParams + feedTuners: FeedTunerFn[] + preferences?: UsePreferencesQueryResponse following: MergeFeedSource_Following customFeeds: MergeFeedSource_Custom[] = [] feedCursor = 0 itemCursor = 0 sampleCursor = 0 - constructor( - public params: FeedParams, - public feedTuners: FeedTunerFn[], - public preferences: UsePreferencesQueryResponse, - ) { + constructor({ + feedParams, + feedTuners, + preferences, + }: { + feedParams: FeedParams + feedTuners: FeedTunerFn[] + preferences?: UsePreferencesQueryResponse + }) { + this.params = feedParams + this.feedTuners = feedTuners + this.preferences = preferences this.following = new MergeFeedSource_Following(this.feedTuners) } @@ -40,7 +50,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({ + feedUri, + feedTuners: this.feedTuners, + preferences: this.preferences, + }), ), ) } else { @@ -223,13 +238,21 @@ class MergeFeedSource_Following extends MergeFeedSource { class MergeFeedSource_Custom extends MergeFeedSource { minDate: Date + feedUri: string + preferences?: UsePreferencesQueryResponse - constructor( - public feedUri: string, - public feedTuners: FeedTunerFn[], - public preferences?: UsePreferencesQueryResponse, - ) { + constructor({ + feedUri, + feedTuners, + preferences, + }: { + feedUri: string + feedTuners: FeedTunerFn[] + preferences?: UsePreferencesQueryResponse + }) { super(feedTuners) + this.feedUri = feedUri + this.preferences = preferences this.sourceInfo = { $type: 'reasonFeedSource', uri: feedUri, diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index bdc611b6df..9127584916 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -385,12 +385,15 @@ function createApi({ feedDesc: FeedDescriptor feedParams: FeedParams feedTuners: FeedTunerFn[] - preferences: UsePreferencesQueryResponse + preferences?: UsePreferencesQueryResponse }) { if (feedDesc === 'home') { if (feedParams.mergeFeedEnabled) { - console.log('MERGE') - return new MergeFeedAPI(feedParams, feedTuners, preferences) + return new MergeFeedAPI({ + feedParams, + feedTuners, + preferences, + }) } else { return new HomeFeedAPI({preferences}) } @@ -404,7 +407,10 @@ function createApi({ return new LikesFeedAPI({actor}) } else if (feedDesc.startsWith('feedgen')) { const [_, feed] = feedDesc.split('|') - return new CustomFeedAPI({feed}, preferences) + return new CustomFeedAPI({ + feedParams: {feed}, + preferences, + }) } else if (feedDesc.startsWith('list')) { const [_, list] = feedDesc.split('|') return new ListFeedAPI({list}) From f0b97e3541e3da562a25238a87b93d762fa635a8 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 25 Apr 2024 09:36:47 -0500 Subject: [PATCH 3/6] Clean up --- src/lib/api/feed/custom.ts | 16 ++++++++-------- src/lib/api/feed/home.ts | 11 +++++------ src/lib/api/feed/merge.ts | 26 +++++++++++++------------- src/lib/api/feed/utils.ts | 6 ++++++ src/state/queries/post-feed.ts | 9 ++++++--- src/state/queries/suggested-follows.ts | 25 +++++++++++++++++++------ 6 files changed, 57 insertions(+), 36 deletions(-) diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts index 24693bfba8..1ca61c109f 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -5,24 +5,23 @@ import { } from '@atproto/api' import {getContentLanguages} from '#/state/preferences/languages' -import {UsePreferencesQueryResponse} from '#/state/queries/preferences' import {getAgent} from '#/state/session' import {FeedAPI, FeedAPIResponse} from './types' -import {aggregateUserInterests, isBlueskyOwnedFeed} from './utils' +import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' export class CustomFeedAPI implements FeedAPI { params: GetCustomFeed.QueryParams - preferences?: UsePreferencesQueryResponse + userInterests?: string constructor({ feedParams, - preferences, + userInterests, }: { feedParams: GetCustomFeed.QueryParams - preferences?: UsePreferencesQueryResponse + userInterests?: string }) { this.params = feedParams - this.preferences = preferences + this.userInterests = userInterests } async peekLatest(): Promise { @@ -47,7 +46,6 @@ export class CustomFeedAPI implements FeedAPI { const contentLangs = getContentLanguages().join(',') const agent = getAgent() const isBlueskyOwned = isBlueskyOwnedFeed(this.params.feed) - const interests = aggregateUserInterests(this.preferences) const res = agent.session ? await getAgent().app.bsky.feed.getFeed( @@ -58,7 +56,9 @@ export class CustomFeedAPI implements FeedAPI { }, { headers: { - 'X-Bsky-Topics': isBlueskyOwned ? interests : '', + ...(isBlueskyOwned + ? createBskyTopicsHeader(this.userInterests) + : {}), 'Accept-Language': contentLangs, }, }, diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index f23058470d..2a89751d5d 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -1,7 +1,6 @@ import {AppBskyFeedDefs} from '@atproto/api' import {PROD_DEFAULT_FEED} from '#/lib/constants' -import {UsePreferencesQueryResponse} from '#/state/queries/preferences' import {CustomFeedAPI} from './custom' import {FollowingFeedAPI} from './following' import {FeedAPI, FeedAPIResponse} from './types' @@ -32,14 +31,14 @@ export class HomeFeedAPI implements FeedAPI { discover: CustomFeedAPI usingDiscover = false itemCursor = 0 - preferences?: UsePreferencesQueryResponse + userInterests?: string - constructor({preferences}: {preferences?: UsePreferencesQueryResponse}) { - this.preferences = preferences + constructor({userInterests}: {userInterests?: string}) { + this.userInterests = userInterests this.following = new FollowingFeedAPI() this.discover = new CustomFeedAPI({ feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, - preferences, + userInterests, }) } @@ -47,7 +46,7 @@ export class HomeFeedAPI implements FeedAPI { this.following = new FollowingFeedAPI() this.discover = new CustomFeedAPI({ feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, - preferences: this.preferences, + userInterests: this.userInterests, }) this.usingDiscover = false this.itemCursor = 0 diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index 7d9037851e..3084800ea0 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -3,7 +3,6 @@ import shuffle from 'lodash.shuffle' import {getContentLanguages} from '#/state/preferences/languages' import {FeedParams} from '#/state/queries/post-feed' -import {UsePreferencesQueryResponse} from '#/state/queries/preferences' import {getAgent} from '#/state/session' import {bundleAsync} from 'lib/async/bundle' import {timeout} from 'lib/async/timeout' @@ -11,7 +10,7 @@ import {feedUriToHref} from 'lib/strings/url-helpers' import {FeedTuner} from '../feed-manip' import {FeedTunerFn} from '../feed-manip' import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types' -import {aggregateUserInterests, isBlueskyOwnedFeed} from './utils' +import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' const REQUEST_WAIT_MS = 500 // 500ms const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours @@ -19,7 +18,7 @@ const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours export class MergeFeedAPI implements FeedAPI { params: FeedParams feedTuners: FeedTunerFn[] - preferences?: UsePreferencesQueryResponse + userInterests?: string following: MergeFeedSource_Following customFeeds: MergeFeedSource_Custom[] = [] feedCursor = 0 @@ -29,15 +28,15 @@ export class MergeFeedAPI implements FeedAPI { constructor({ feedParams, feedTuners, - preferences, + userInterests, }: { feedParams: FeedParams feedTuners: FeedTunerFn[] - preferences?: UsePreferencesQueryResponse + userInterests?: string }) { this.params = feedParams this.feedTuners = feedTuners - this.preferences = preferences + this.userInterests = userInterests this.following = new MergeFeedSource_Following(this.feedTuners) } @@ -54,7 +53,7 @@ export class MergeFeedAPI implements FeedAPI { new MergeFeedSource_Custom({ feedUri, feedTuners: this.feedTuners, - preferences: this.preferences, + userInterests: this.userInterests, }), ), ) @@ -239,20 +238,20 @@ class MergeFeedSource_Following extends MergeFeedSource { class MergeFeedSource_Custom extends MergeFeedSource { minDate: Date feedUri: string - preferences?: UsePreferencesQueryResponse + userInterests?: string constructor({ feedUri, feedTuners, - preferences, + userInterests, }: { feedUri: string feedTuners: FeedTunerFn[] - preferences?: UsePreferencesQueryResponse + userInterests?: string }) { super(feedTuners) this.feedUri = feedUri - this.preferences = preferences + this.userInterests = userInterests this.sourceInfo = { $type: 'reasonFeedSource', uri: feedUri, @@ -268,7 +267,6 @@ class MergeFeedSource_Custom extends MergeFeedSource { try { const contentLangs = getContentLanguages().join(',') const isBlueskyOwned = isBlueskyOwnedFeed(this.feedUri) - const interests = aggregateUserInterests(this.preferences) const res = await getAgent().app.bsky.feed.getFeed( { cursor, @@ -277,7 +275,9 @@ class MergeFeedSource_Custom extends MergeFeedSource { }, { headers: { - 'X-Bsky-Topics': isBlueskyOwned ? interests : '', + ...(isBlueskyOwned + ? createBskyTopicsHeader(this.userInterests) + : {}), 'Accept-Language': contentLangs, }, }, diff --git a/src/lib/api/feed/utils.ts b/src/lib/api/feed/utils.ts index a42a592f1a..50162ed2a4 100644 --- a/src/lib/api/feed/utils.ts +++ b/src/lib/api/feed/utils.ts @@ -3,6 +3,12 @@ import {AtUri} from '@atproto/api' import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' import {UsePreferencesQueryResponse} from '#/state/queries/preferences' +export function createBskyTopicsHeader(userInterests?: string) { + return { + 'X-Bsky-Topics': userInterests || '', + } +} + export function aggregateUserInterests( preferences?: UsePreferencesQueryResponse, ) { diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 9127584916..4400937e1c 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -15,6 +15,7 @@ import { } from '@tanstack/react-query' import {HomeFeedAPI} from '#/lib/api/feed/home' +import {aggregateUserInterests} from '#/lib/api/feed/utils' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import {logger} from '#/logger' import {STALE} from '#/state/queries' @@ -387,15 +388,17 @@ function createApi({ feedTuners: FeedTunerFn[] preferences?: UsePreferencesQueryResponse }) { + const userInterests = aggregateUserInterests(preferences) + if (feedDesc === 'home') { if (feedParams.mergeFeedEnabled) { return new MergeFeedAPI({ feedParams, feedTuners, - preferences, + userInterests, }) } else { - return new HomeFeedAPI({preferences}) + return new HomeFeedAPI({userInterests}) } } else if (feedDesc === 'following') { return new FollowingFeedAPI() @@ -409,7 +412,7 @@ function createApi({ const [_, feed] = feedDesc.split('|') return new CustomFeedAPI({ feedParams: {feed}, - preferences, + userInterests, }) } else if (feedDesc.startsWith('list')) { const [_, list] = feedDesc.split('|') diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 2573927b20..4177aa29e1 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -12,8 +12,15 @@ import { useQuery, } from '@tanstack/react-query' +import { + aggregateUserInterests, + createBskyTopicsHeader, +} from '#/lib/api/feed/utils' import {STALE} from '#/state/queries' -import {useModerationOpts} from '#/state/queries/preferences' +import { + useModerationOpts, + usePreferencesQuery, +} from '#/state/queries/preferences' import {getAgent, useSession} from '#/state/session' const suggestedFollowsQueryKeyRoot = 'suggested-follows' @@ -28,6 +35,7 @@ const suggestedFollowsByActorQueryKey = (did: string) => [ export function useSuggestedFollowsQuery() { const {currentAccount} = useSession() const moderationOpts = useModerationOpts() + const {data: preferences} = usePreferencesQuery() return useInfiniteQuery< AppBskyActorGetSuggestions.OutputSchema, @@ -36,14 +44,19 @@ export function useSuggestedFollowsQuery() { QueryKey, string | undefined >({ - enabled: !!moderationOpts, + enabled: !!moderationOpts && !!preferences, staleTime: STALE.HOURS.ONE, queryKey: suggestedFollowsQueryKey, queryFn: async ({pageParam}) => { - const res = await getAgent().app.bsky.actor.getSuggestions({ - limit: 25, - cursor: pageParam, - }) + const res = await getAgent().app.bsky.actor.getSuggestions( + { + limit: 25, + cursor: pageParam, + }, + { + headers: createBskyTopicsHeader(aggregateUserInterests(preferences)), + }, + ) res.data.actors = res.data.actors .filter( From 3fbfeb4c021af802d7959ea95f7a9134d93c7f56 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 25 Apr 2024 09:43:45 -0500 Subject: [PATCH 4/6] Tweak --- src/state/queries/suggested-follows.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 4177aa29e1..4cbb78f2b5 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -16,6 +16,7 @@ import { aggregateUserInterests, createBskyTopicsHeader, } from '#/lib/api/feed/utils' +import {getContentLanguages} from '#/state/preferences/languages' import {STALE} from '#/state/queries' import { useModerationOpts, @@ -48,13 +49,17 @@ export function useSuggestedFollowsQuery() { staleTime: STALE.HOURS.ONE, queryKey: suggestedFollowsQueryKey, queryFn: async ({pageParam}) => { + const contentLangs = getContentLanguages().join(',') const res = await getAgent().app.bsky.actor.getSuggestions( { limit: 25, cursor: pageParam, }, { - headers: createBskyTopicsHeader(aggregateUserInterests(preferences)), + headers: { + ...createBskyTopicsHeader(aggregateUserInterests(preferences)), + 'Accept-Language': contentLangs, + }, }, ) From 060cbfa17817c1f4c8132cd1b867a87a1d364a3c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 29 Apr 2024 21:32:33 +0100 Subject: [PATCH 5/6] Rm extra assignment --- src/lib/api/feed/merge.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index 6f228d9156..b7ac8bce1c 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -286,7 +286,6 @@ class MergeFeedSource_Custom extends MergeFeedSource { href: feedUriToHref(feedUri), } this.minDate = new Date(Date.now() - POST_AGE_CUTOFF) - this.userInterests = userInterests } protected async _getFeed( From 3b151fa2fbb666f24eaec8cb4f8fa290a6a064b5 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 29 Apr 2024 22:01:45 +0100 Subject: [PATCH 6/6] Narrow down the argument --- src/state/queries/post-feed.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index a60774b6a3..c265cecd60 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -32,11 +32,7 @@ import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip' import {BSKY_FEED_OWNER_DIDS} from 'lib/constants' import {KnownError} from '#/view/com/posts/FeedErrorMessage' import {useFeedTuners} from '../preferences/feed-tuners' -import { - useModerationOpts, - usePreferencesQuery, - UsePreferencesQueryResponse, -} from './preferences' +import {useModerationOpts, usePreferencesQuery} from './preferences' import {embedViewRecordToPostView, getEmbeddedPost} from './util' type ActorDid = string @@ -110,6 +106,7 @@ export function usePostFeedQuery( const {data: preferences} = usePreferencesQuery() const enabled = opts?.enabled !== false && Boolean(moderationOpts) && Boolean(preferences) + const userInterests = aggregateUserInterests(preferences) const {getAgent} = useAgent() const lastRun = useRef<{ data: InfiniteData @@ -148,7 +145,7 @@ export function usePostFeedQuery( feedDesc, feedParams: params || {}, feedTuners, - preferences, + userInterests, // Not in the query key because they don't change. getAgent, }), cursor: undefined, @@ -379,17 +376,15 @@ function createApi({ feedDesc, feedParams, feedTuners, - preferences, + userInterests, getAgent, }: { feedDesc: FeedDescriptor feedParams: FeedParams feedTuners: FeedTunerFn[] - preferences?: UsePreferencesQueryResponse + userInterests?: string getAgent: () => BskyAgent }) { - const userInterests = aggregateUserInterests(preferences) - if (feedDesc === 'home') { if (feedParams.mergeFeedEnabled) { return new MergeFeedAPI({