Send Bluesky feeds and suggested follows more data (#3695)
* WIP * Fix constructors * Clean up * Tweak * Rm extra assignment * Narrow down the argument --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>zio/stable
parent
d893fe005d
commit
a4e34537ce
|
@ -7,20 +7,25 @@ import {
|
||||||
|
|
||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {FeedAPI, FeedAPIResponse} from './types'
|
import {FeedAPI, FeedAPIResponse} from './types'
|
||||||
|
import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils'
|
||||||
|
|
||||||
export class CustomFeedAPI implements FeedAPI {
|
export class CustomFeedAPI implements FeedAPI {
|
||||||
getAgent: () => BskyAgent
|
getAgent: () => BskyAgent
|
||||||
params: GetCustomFeed.QueryParams
|
params: GetCustomFeed.QueryParams
|
||||||
|
userInterests?: string
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
getAgent,
|
getAgent,
|
||||||
feedParams,
|
feedParams,
|
||||||
|
userInterests,
|
||||||
}: {
|
}: {
|
||||||
getAgent: () => BskyAgent
|
getAgent: () => BskyAgent
|
||||||
feedParams: GetCustomFeed.QueryParams
|
feedParams: GetCustomFeed.QueryParams
|
||||||
|
userInterests?: string
|
||||||
}) {
|
}) {
|
||||||
this.getAgent = getAgent
|
this.getAgent = getAgent
|
||||||
this.params = feedParams
|
this.params = feedParams
|
||||||
|
this.userInterests = userInterests
|
||||||
}
|
}
|
||||||
|
|
||||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||||
|
@ -44,6 +49,8 @@ export class CustomFeedAPI implements FeedAPI {
|
||||||
}): Promise<FeedAPIResponse> {
|
}): Promise<FeedAPIResponse> {
|
||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
const agent = this.getAgent()
|
const agent = this.getAgent()
|
||||||
|
const isBlueskyOwned = isBlueskyOwnedFeed(this.params.feed)
|
||||||
|
|
||||||
const res = agent.session
|
const res = agent.session
|
||||||
? await this.getAgent().app.bsky.feed.getFeed(
|
? await this.getAgent().app.bsky.feed.getFeed(
|
||||||
{
|
{
|
||||||
|
@ -53,6 +60,9 @@ export class CustomFeedAPI implements FeedAPI {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
...(isBlueskyOwned
|
||||||
|
? createBskyTopicsHeader(this.userInterests)
|
||||||
|
: {}),
|
||||||
'Accept-Language': contentLangs,
|
'Accept-Language': contentLangs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,14 +32,22 @@ export class HomeFeedAPI implements FeedAPI {
|
||||||
discover: CustomFeedAPI
|
discover: CustomFeedAPI
|
||||||
usingDiscover = false
|
usingDiscover = false
|
||||||
itemCursor = 0
|
itemCursor = 0
|
||||||
|
userInterests?: string
|
||||||
|
|
||||||
constructor({getAgent}: {getAgent: () => BskyAgent}) {
|
constructor({
|
||||||
|
userInterests,
|
||||||
|
getAgent,
|
||||||
|
}: {
|
||||||
|
userInterests?: string
|
||||||
|
getAgent: () => BskyAgent
|
||||||
|
}) {
|
||||||
this.getAgent = getAgent
|
this.getAgent = getAgent
|
||||||
this.following = new FollowingFeedAPI({getAgent})
|
this.following = new FollowingFeedAPI({getAgent})
|
||||||
this.discover = new CustomFeedAPI({
|
this.discover = new CustomFeedAPI({
|
||||||
getAgent,
|
getAgent,
|
||||||
feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
|
feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
|
||||||
})
|
})
|
||||||
|
this.userInterests = userInterests
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
@ -47,6 +55,7 @@ export class HomeFeedAPI implements FeedAPI {
|
||||||
this.discover = new CustomFeedAPI({
|
this.discover = new CustomFeedAPI({
|
||||||
getAgent: this.getAgent,
|
getAgent: this.getAgent,
|
||||||
feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
|
feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
|
||||||
|
userInterests: this.userInterests,
|
||||||
})
|
})
|
||||||
this.usingDiscover = false
|
this.usingDiscover = false
|
||||||
this.itemCursor = 0
|
this.itemCursor = 0
|
||||||
|
|
|
@ -9,11 +9,13 @@ import {feedUriToHref} from 'lib/strings/url-helpers'
|
||||||
import {FeedTuner} from '../feed-manip'
|
import {FeedTuner} from '../feed-manip'
|
||||||
import {FeedTunerFn} from '../feed-manip'
|
import {FeedTunerFn} from '../feed-manip'
|
||||||
import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
|
import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
|
||||||
|
import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils'
|
||||||
|
|
||||||
const REQUEST_WAIT_MS = 500 // 500ms
|
const REQUEST_WAIT_MS = 500 // 500ms
|
||||||
const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
|
const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
|
||||||
|
|
||||||
export class MergeFeedAPI implements FeedAPI {
|
export class MergeFeedAPI implements FeedAPI {
|
||||||
|
userInterests?: string
|
||||||
getAgent: () => BskyAgent
|
getAgent: () => BskyAgent
|
||||||
params: FeedParams
|
params: FeedParams
|
||||||
feedTuners: FeedTunerFn[]
|
feedTuners: FeedTunerFn[]
|
||||||
|
@ -27,14 +29,17 @@ export class MergeFeedAPI implements FeedAPI {
|
||||||
getAgent,
|
getAgent,
|
||||||
feedParams,
|
feedParams,
|
||||||
feedTuners,
|
feedTuners,
|
||||||
|
userInterests,
|
||||||
}: {
|
}: {
|
||||||
getAgent: () => BskyAgent
|
getAgent: () => BskyAgent
|
||||||
feedParams: FeedParams
|
feedParams: FeedParams
|
||||||
feedTuners: FeedTunerFn[]
|
feedTuners: FeedTunerFn[]
|
||||||
|
userInterests?: string
|
||||||
}) {
|
}) {
|
||||||
this.getAgent = getAgent
|
this.getAgent = getAgent
|
||||||
this.params = feedParams
|
this.params = feedParams
|
||||||
this.feedTuners = feedTuners
|
this.feedTuners = feedTuners
|
||||||
|
this.userInterests = userInterests
|
||||||
this.following = new MergeFeedSource_Following({
|
this.following = new MergeFeedSource_Following({
|
||||||
getAgent: this.getAgent,
|
getAgent: this.getAgent,
|
||||||
feedTuners: this.feedTuners,
|
feedTuners: this.feedTuners,
|
||||||
|
@ -58,6 +63,7 @@ export class MergeFeedAPI implements FeedAPI {
|
||||||
getAgent: this.getAgent,
|
getAgent: this.getAgent,
|
||||||
feedUri,
|
feedUri,
|
||||||
feedTuners: this.feedTuners,
|
feedTuners: this.feedTuners,
|
||||||
|
userInterests: this.userInterests,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -254,15 +260,18 @@ class MergeFeedSource_Custom extends MergeFeedSource {
|
||||||
getAgent: () => BskyAgent
|
getAgent: () => BskyAgent
|
||||||
minDate: Date
|
minDate: Date
|
||||||
feedUri: string
|
feedUri: string
|
||||||
|
userInterests?: string
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
getAgent,
|
getAgent,
|
||||||
feedUri,
|
feedUri,
|
||||||
feedTuners,
|
feedTuners,
|
||||||
|
userInterests,
|
||||||
}: {
|
}: {
|
||||||
getAgent: () => BskyAgent
|
getAgent: () => BskyAgent
|
||||||
feedUri: string
|
feedUri: string
|
||||||
feedTuners: FeedTunerFn[]
|
feedTuners: FeedTunerFn[]
|
||||||
|
userInterests?: string
|
||||||
}) {
|
}) {
|
||||||
super({
|
super({
|
||||||
getAgent,
|
getAgent,
|
||||||
|
@ -270,6 +279,7 @@ class MergeFeedSource_Custom extends MergeFeedSource {
|
||||||
})
|
})
|
||||||
this.getAgent = getAgent
|
this.getAgent = getAgent
|
||||||
this.feedUri = feedUri
|
this.feedUri = feedUri
|
||||||
|
this.userInterests = userInterests
|
||||||
this.sourceInfo = {
|
this.sourceInfo = {
|
||||||
$type: 'reasonFeedSource',
|
$type: 'reasonFeedSource',
|
||||||
uri: feedUri,
|
uri: feedUri,
|
||||||
|
@ -284,6 +294,7 @@ class MergeFeedSource_Custom extends MergeFeedSource {
|
||||||
): Promise<AppBskyFeedGetTimeline.Response> {
|
): Promise<AppBskyFeedGetTimeline.Response> {
|
||||||
try {
|
try {
|
||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
|
const isBlueskyOwned = isBlueskyOwnedFeed(this.feedUri)
|
||||||
const res = await this.getAgent().app.bsky.feed.getFeed(
|
const res = await this.getAgent().app.bsky.feed.getFeed(
|
||||||
{
|
{
|
||||||
cursor,
|
cursor,
|
||||||
|
@ -292,6 +303,9 @@ class MergeFeedSource_Custom extends MergeFeedSource {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
...(isBlueskyOwned
|
||||||
|
? createBskyTopicsHeader(this.userInterests)
|
||||||
|
: {}),
|
||||||
'Accept-Language': contentLangs,
|
'Accept-Language': contentLangs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
return preferences?.interests?.tags?.join(',') || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isBlueskyOwnedFeed(feedUri: string) {
|
||||||
|
const uri = new AtUri(feedUri)
|
||||||
|
return BSKY_FEED_OWNER_DIDS.includes(uri.host)
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {HomeFeedAPI} from '#/lib/api/feed/home'
|
import {HomeFeedAPI} from '#/lib/api/feed/home'
|
||||||
|
import {aggregateUserInterests} from '#/lib/api/feed/utils'
|
||||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
|
@ -31,7 +32,7 @@ import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip'
|
||||||
import {BSKY_FEED_OWNER_DIDS} from 'lib/constants'
|
import {BSKY_FEED_OWNER_DIDS} from 'lib/constants'
|
||||||
import {KnownError} from '#/view/com/posts/FeedErrorMessage'
|
import {KnownError} from '#/view/com/posts/FeedErrorMessage'
|
||||||
import {useFeedTuners} from '../preferences/feed-tuners'
|
import {useFeedTuners} from '../preferences/feed-tuners'
|
||||||
import {useModerationOpts} from './preferences'
|
import {useModerationOpts, usePreferencesQuery} from './preferences'
|
||||||
import {embedViewRecordToPostView, getEmbeddedPost} from './util'
|
import {embedViewRecordToPostView, getEmbeddedPost} from './util'
|
||||||
|
|
||||||
type ActorDid = string
|
type ActorDid = string
|
||||||
|
@ -102,8 +103,11 @@ export function usePostFeedQuery(
|
||||||
) {
|
) {
|
||||||
const feedTuners = useFeedTuners(feedDesc)
|
const feedTuners = useFeedTuners(feedDesc)
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
const enabled =
|
||||||
|
opts?.enabled !== false && Boolean(moderationOpts) && Boolean(preferences)
|
||||||
|
const userInterests = aggregateUserInterests(preferences)
|
||||||
const {getAgent} = useAgent()
|
const {getAgent} = useAgent()
|
||||||
const enabled = opts?.enabled !== false && Boolean(moderationOpts)
|
|
||||||
const lastRun = useRef<{
|
const lastRun = useRef<{
|
||||||
data: InfiniteData<FeedPageUnselected>
|
data: InfiniteData<FeedPageUnselected>
|
||||||
args: typeof selectArgs
|
args: typeof selectArgs
|
||||||
|
@ -141,6 +145,7 @@ export function usePostFeedQuery(
|
||||||
feedDesc,
|
feedDesc,
|
||||||
feedParams: params || {},
|
feedParams: params || {},
|
||||||
feedTuners,
|
feedTuners,
|
||||||
|
userInterests, // Not in the query key because they don't change.
|
||||||
getAgent,
|
getAgent,
|
||||||
}),
|
}),
|
||||||
cursor: undefined,
|
cursor: undefined,
|
||||||
|
@ -371,11 +376,13 @@ function createApi({
|
||||||
feedDesc,
|
feedDesc,
|
||||||
feedParams,
|
feedParams,
|
||||||
feedTuners,
|
feedTuners,
|
||||||
|
userInterests,
|
||||||
getAgent,
|
getAgent,
|
||||||
}: {
|
}: {
|
||||||
feedDesc: FeedDescriptor
|
feedDesc: FeedDescriptor
|
||||||
feedParams: FeedParams
|
feedParams: FeedParams
|
||||||
feedTuners: FeedTunerFn[]
|
feedTuners: FeedTunerFn[]
|
||||||
|
userInterests?: string
|
||||||
getAgent: () => BskyAgent
|
getAgent: () => BskyAgent
|
||||||
}) {
|
}) {
|
||||||
if (feedDesc === 'home') {
|
if (feedDesc === 'home') {
|
||||||
|
@ -384,9 +391,10 @@ function createApi({
|
||||||
getAgent,
|
getAgent,
|
||||||
feedParams,
|
feedParams,
|
||||||
feedTuners,
|
feedTuners,
|
||||||
|
userInterests,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return new HomeFeedAPI({getAgent})
|
return new HomeFeedAPI({getAgent, userInterests})
|
||||||
}
|
}
|
||||||
} else if (feedDesc === 'following') {
|
} else if (feedDesc === 'following') {
|
||||||
return new FollowingFeedAPI({getAgent})
|
return new FollowingFeedAPI({getAgent})
|
||||||
|
@ -401,6 +409,7 @@ function createApi({
|
||||||
return new CustomFeedAPI({
|
return new CustomFeedAPI({
|
||||||
getAgent,
|
getAgent,
|
||||||
feedParams: {feed},
|
feedParams: {feed},
|
||||||
|
userInterests,
|
||||||
})
|
})
|
||||||
} else if (feedDesc.startsWith('list')) {
|
} else if (feedDesc.startsWith('list')) {
|
||||||
const [_, list] = feedDesc.split('|')
|
const [_, list] = feedDesc.split('|')
|
||||||
|
|
|
@ -12,8 +12,16 @@ import {
|
||||||
useQuery,
|
useQuery,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {
|
||||||
|
aggregateUserInterests,
|
||||||
|
createBskyTopicsHeader,
|
||||||
|
} from '#/lib/api/feed/utils'
|
||||||
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {useModerationOpts} from '#/state/queries/preferences'
|
import {
|
||||||
|
useModerationOpts,
|
||||||
|
usePreferencesQuery,
|
||||||
|
} from '#/state/queries/preferences'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {useAgent, useSession} from '#/state/session'
|
||||||
|
|
||||||
const suggestedFollowsQueryKeyRoot = 'suggested-follows'
|
const suggestedFollowsQueryKeyRoot = 'suggested-follows'
|
||||||
|
@ -29,6 +37,7 @@ export function useSuggestedFollowsQuery() {
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {getAgent} = useAgent()
|
const {getAgent} = useAgent()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
|
||||||
return useInfiniteQuery<
|
return useInfiniteQuery<
|
||||||
AppBskyActorGetSuggestions.OutputSchema,
|
AppBskyActorGetSuggestions.OutputSchema,
|
||||||
|
@ -37,14 +46,23 @@ export function useSuggestedFollowsQuery() {
|
||||||
QueryKey,
|
QueryKey,
|
||||||
string | undefined
|
string | undefined
|
||||||
>({
|
>({
|
||||||
enabled: !!moderationOpts,
|
enabled: !!moderationOpts && !!preferences,
|
||||||
staleTime: STALE.HOURS.ONE,
|
staleTime: STALE.HOURS.ONE,
|
||||||
queryKey: suggestedFollowsQueryKey,
|
queryKey: suggestedFollowsQueryKey,
|
||||||
queryFn: async ({pageParam}) => {
|
queryFn: async ({pageParam}) => {
|
||||||
const res = await getAgent().app.bsky.actor.getSuggestions({
|
const contentLangs = getContentLanguages().join(',')
|
||||||
limit: 25,
|
const res = await getAgent().app.bsky.actor.getSuggestions(
|
||||||
cursor: pageParam,
|
{
|
||||||
})
|
limit: 25,
|
||||||
|
cursor: pageParam,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
...createBskyTopicsHeader(aggregateUserInterests(preferences)),
|
||||||
|
'Accept-Language': contentLangs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
res.data.actors = res.data.actors
|
res.data.actors = res.data.actors
|
||||||
.filter(
|
.filter(
|
||||||
|
|
Loading…
Reference in New Issue