Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestions skeleton impl #2403

Merged
merged 14 commits into from
Apr 16, 2024
44 changes: 44 additions & 0 deletions lexicons/app/bsky/unspecced/getSuggestionsSkeleton.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestionsSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested actors. Intended to be called and then hydrated through app.bsky.actor.getSuggestions",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 50
},
"cursor": { "type": "string" }
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"cursor": { "type": "string" },
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.unspecced.defs#skeletonSearchActor"
}
}
}
}
}
}
}
}
18 changes: 18 additions & 0 deletions packages/api/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/up
import * as AppBskyRichtextFacet from './types/app/bsky/richtext/facet'
import * as AppBskyUnspeccedDefs from './types/app/bsky/unspecced/defs'
import * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators'
import * as AppBskyUnspeccedGetSuggestionsSkeleton from './types/app/bsky/unspecced/getSuggestionsSkeleton'
import * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecced/getTaggedSuggestions'
import * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton'
import * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton'
Expand Down Expand Up @@ -310,6 +311,7 @@ export * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/up
export * as AppBskyRichtextFacet from './types/app/bsky/richtext/facet'
export * as AppBskyUnspeccedDefs from './types/app/bsky/unspecced/defs'
export * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators'
export * as AppBskyUnspeccedGetSuggestionsSkeleton from './types/app/bsky/unspecced/getSuggestionsSkeleton'
export * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecced/getTaggedSuggestions'
export * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton'
export * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton'
Expand Down Expand Up @@ -2635,6 +2637,22 @@ export class AppBskyUnspeccedNS {
})
}

getSuggestionsSkeleton(
params?: AppBskyUnspeccedGetSuggestionsSkeleton.QueryParams,
opts?: AppBskyUnspeccedGetSuggestionsSkeleton.CallOptions,
): Promise<AppBskyUnspeccedGetSuggestionsSkeleton.Response> {
return this._service.xrpc
.call(
'app.bsky.unspecced.getSuggestionsSkeleton',
params,
undefined,
opts,
)
.catch((e) => {
throw AppBskyUnspeccedGetSuggestionsSkeleton.toKnownErr(e)
})
}

getTaggedSuggestions(
params?: AppBskyUnspeccedGetTaggedSuggestions.QueryParams,
opts?: AppBskyUnspeccedGetTaggedSuggestions.CallOptions,
Expand Down
52 changes: 52 additions & 0 deletions packages/api/src/client/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7849,6 +7849,56 @@ export const schemaDict = {
},
},
},
AppBskyUnspeccedGetSuggestionsSkeleton: {
lexicon: 1,
id: 'app.bsky.unspecced.getSuggestionsSkeleton',
defs: {
main: {
type: 'query',
description:
'Get a skeleton of suggested actors. Intended to be called and then hydrated through app.bsky.actor.getSuggestions',
parameters: {
type: 'params',
properties: {
viewer: {
type: 'string',
format: 'did',
description:
'DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking.',
},
limit: {
type: 'integer',
minimum: 1,
maximum: 100,
default: 50,
},
cursor: {
type: 'string',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['actors'],
properties: {
cursor: {
type: 'string',
},
actors: {
type: 'array',
items: {
type: 'ref',
ref: 'lex:app.bsky.unspecced.defs#skeletonSearchActor',
},
},
},
},
},
},
},
},
AppBskyUnspeccedGetTaggedSuggestions: {
lexicon: 1,
id: 'app.bsky.unspecced.getTaggedSuggestions',
Expand Down Expand Up @@ -9627,6 +9677,8 @@ export const ids = {
AppBskyUnspeccedDefs: 'app.bsky.unspecced.defs',
AppBskyUnspeccedGetPopularFeedGenerators:
'app.bsky.unspecced.getPopularFeedGenerators',
AppBskyUnspeccedGetSuggestionsSkeleton:
'app.bsky.unspecced.getSuggestionsSkeleton',
AppBskyUnspeccedGetTaggedSuggestions:
'app.bsky.unspecced.getTaggedSuggestions',
AppBskyUnspeccedSearchActorsSkeleton:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { Headers, XRPCError } from '@atproto/xrpc'
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { isObj, hasProp } from '../../../../util'
import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
import * as AppBskyUnspeccedDefs from './defs'

export interface QueryParams {
/** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. */
viewer?: string
limit?: number
cursor?: string
}

export type InputSchema = undefined

export interface OutputSchema {
cursor?: string
actors: AppBskyUnspeccedDefs.SkeletonSearchActor[]
[k: string]: unknown
}

export interface CallOptions {
headers?: Headers
}

export interface Response {
success: boolean
headers: Headers
data: OutputSchema
}

export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
}
return e
}
41 changes: 28 additions & 13 deletions packages/bsky/src/api/app/bsky/actor/getSuggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Views } from '../../../../views'
import { DataPlaneClient } from '../../../../data-plane'
import { parseString } from '../../../../hydration/util'
import { resHeaders } from '../../../util'
import AtpAgent from '@atproto/api'

export default function (server: Server, ctx: AppContext) {
const getSuggestions = createPipeline(
Expand Down Expand Up @@ -43,21 +44,34 @@ const skeleton = async (input: {
}): Promise<Skeleton> => {
const { ctx, params } = input
const viewer = params.hydrateCtx.viewer
// @NOTE for appview swap moving to rkey-based cursors which are somewhat permissive, should not hard-break pagination
const suggestions = await ctx.dataplane.getFollowSuggestions({
actorDid: viewer ?? undefined,
cursor: params.cursor,
limit: params.limit,
})
let dids = suggestions.dids
if (viewer !== null) {
const follows = await ctx.dataplane.getActorFollowsActors({
actorDid: viewer,
targetDids: dids,
if (ctx.suggestionsAgent) {
const res =
await ctx.suggestionsAgent.api.app.bsky.unspecced.getSuggestionsSkeleton({
viewer: viewer ?? undefined,
limit: params.limit,
cursor: params.cursor,
})
return {
dids: res.data.actors.map((a) => a.did),
cursor: res.data.cursor,
}
} else {
// @NOTE for appview swap moving to rkey-based cursors which are somewhat permissive, should not hard-break pagination
const suggestions = await ctx.dataplane.getFollowSuggestions({
actorDid: viewer ?? undefined,
cursor: params.cursor,
limit: params.limit,
})
dids = dids.filter((did, i) => !follows.uris[i] && did !== viewer)
let dids = suggestions.dids
if (viewer !== null) {
const follows = await ctx.dataplane.getActorFollowsActors({
actorDid: viewer,
targetDids: dids,
})
dids = dids.filter((did, i) => !follows.uris[i] && did !== viewer)
}
return { dids, cursor: parseString(suggestions.cursor) }
}
return { dids, cursor: parseString(suggestions.cursor) }
}

const hydration = async (input: {
Expand Down Expand Up @@ -101,6 +115,7 @@ const presentation = (input: {
}

type Context = {
suggestionsAgent: AtpAgent | undefined
dataplane: DataPlaneClient
hydrator: Hydrator
views: Views
Expand Down
14 changes: 14 additions & 0 deletions packages/bsky/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface ServerConfigValues {
courierHttpVersion?: '1.1' | '2'
courierIgnoreBadTls?: boolean
searchUrl?: string
suggestionsUrl?: string
suggestionsApiKey?: string
cdnUrl?: string
blobRateLimitBypassKey?: string
blobRateLimitBypassHostname?: string
Expand Down Expand Up @@ -55,6 +57,8 @@ export class ServerConfig {
process.env.BSKY_SEARCH_URL ||
process.env.BSKY_SEARCH_ENDPOINT ||
undefined
const suggestionsUrl = process.env.BSKY_SUGGESTIONS_URL || undefined
const suggestionsApiKey = process.env.BSKY_SUGGESTIONS_API_KEY || undefined
let dataplaneUrls = overrides?.dataplaneUrls
dataplaneUrls ??= process.env.BSKY_DATAPLANE_URLS
? process.env.BSKY_DATAPLANE_URLS.split(',')
Expand Down Expand Up @@ -104,6 +108,8 @@ export class ServerConfig {
dataplaneHttpVersion,
dataplaneIgnoreBadTls,
searchUrl,
suggestionsUrl,
suggestionsApiKey,
didPlcUrl,
labelsFromIssuerDids,
handleResolveNameservers,
Expand Down Expand Up @@ -206,6 +212,14 @@ export class ServerConfig {
return this.cfg.searchUrl
}

get suggestionsUrl() {
return this.cfg.suggestionsUrl
}

get suggestionsApiKey() {
return this.cfg.suggestionsApiKey
}

get cdnUrl() {
return this.cfg.cdnUrl
}
Expand Down
5 changes: 5 additions & 0 deletions packages/bsky/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class AppContext {
cfg: ServerConfig
dataplane: DataPlaneClient
searchAgent: AtpAgent | undefined
suggestionsAgent: AtpAgent | undefined
hydrator: Hydrator
views: Views
signingKey: Keypair
Expand All @@ -46,6 +47,10 @@ export class AppContext {
return this.opts.searchAgent
}

get suggestionsAgent(): AtpAgent | undefined {
return this.opts.suggestionsAgent
}

get hydrator(): Hydrator {
return this.opts.hydrator
}
Expand Down
12 changes: 12 additions & 0 deletions packages/bsky/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ export class BskyAppView {
const searchAgent = config.searchUrl
? new AtpAgent({ service: config.searchUrl })
: undefined

const suggestionsAgent = config.suggestionsUrl
? new AtpAgent({ service: config.suggestionsUrl })
: undefined
if (suggestionsAgent && config.suggestionsApiKey) {
suggestionsAgent.api.setHeader(
'authorization',
`Bearer ${config.suggestionsApiKey}`,
)
}

const dataplane = createDataPlaneClient(config.dataplaneUrls, {
httpVersion: config.dataplaneHttpVersion,
rejectUnauthorized: !config.dataplaneIgnoreBadTls,
Expand Down Expand Up @@ -107,6 +118,7 @@ export class BskyAppView {
cfg: config,
dataplane,
searchAgent,
suggestionsAgent,
hydrator,
views,
signingKey,
Expand Down
12 changes: 12 additions & 0 deletions packages/bsky/src/lexicon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ import * as AppBskyNotificationListNotifications from './types/app/bsky/notifica
import * as AppBskyNotificationRegisterPush from './types/app/bsky/notification/registerPush'
import * as AppBskyNotificationUpdateSeen from './types/app/bsky/notification/updateSeen'
import * as AppBskyUnspeccedGetPopularFeedGenerators from './types/app/bsky/unspecced/getPopularFeedGenerators'
import * as AppBskyUnspeccedGetSuggestionsSkeleton from './types/app/bsky/unspecced/getSuggestionsSkeleton'
import * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecced/getTaggedSuggestions'
import * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton'
import * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton'
Expand Down Expand Up @@ -1648,6 +1649,17 @@ export class AppBskyUnspeccedNS {
return this._server.xrpc.method(nsid, cfg)
}

getSuggestionsSkeleton<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
AppBskyUnspeccedGetSuggestionsSkeleton.Handler<ExtractAuth<AV>>,
AppBskyUnspeccedGetSuggestionsSkeleton.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'app.bsky.unspecced.getSuggestionsSkeleton' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}

getTaggedSuggestions<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
Expand Down
Loading
Loading