diff --git a/.changeset/chilly-terms-add.md b/.changeset/chilly-terms-add.md new file mode 100644 index 00000000000..0a92527fc84 --- /dev/null +++ b/.changeset/chilly-terms-add.md @@ -0,0 +1,5 @@ +--- +'@atproto/api': patch +--- + +Added feed generator interaction lexicons diff --git a/lexicons/app/bsky/feed/defs.json b/lexicons/app/bsky/feed/defs.json index 0a83fa71a2a..fec37f7013d 100644 --- a/lexicons/app/bsky/feed/defs.json +++ b/lexicons/app/bsky/feed/defs.json @@ -49,7 +49,12 @@ "properties": { "post": { "type": "ref", "ref": "#postView" }, "reply": { "type": "ref", "ref": "#replyRef" }, - "reason": { "type": "union", "refs": ["#reasonRepost"] } + "reason": { "type": "union", "refs": ["#reasonRepost"] }, + "feedContext": { + "type": "string", + "description": "Context provided by feed generator that may be passed back alongside interactions.", + "maxLength": 2000 + } } }, "replyRef": { @@ -137,6 +142,7 @@ }, "avatar": { "type": "string", "format": "uri" }, "likeCount": { "type": "integer", "minimum": 0 }, + "acceptsInteractions": { "type": "boolean" }, "labels": { "type": "array", "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } @@ -156,7 +162,12 @@ "required": ["post"], "properties": { "post": { "type": "string", "format": "at-uri" }, - "reason": { "type": "union", "refs": ["#skeletonReasonRepost"] } + "reason": { "type": "union", "refs": ["#skeletonReasonRepost"] }, + "feedContext": { + "type": "string", + "description": "Context that will be passed through to client and may be passed to feed generator back alongside interactions.", + "maxLength": 2000 + } } }, "skeletonReasonRepost": { @@ -177,6 +188,82 @@ "items": { "type": "ref", "ref": "app.bsky.graph.defs#listViewBasic" } } } + }, + "interaction": { + "type": "object", + "properties": { + "item": { "type": "string", "format": "at-uri" }, + "event": { + "type": "string", + "knownValues": [ + "app.bsky.feed.defs#requestLess", + "app.bsky.feed.defs#requestMore", + "app.bsky.feed.defs#clickthroughItem", + "app.bsky.feed.defs#clickthroughAuthor", + "app.bsky.feed.defs#clickthroughReposter", + "app.bsky.feed.defs#clickthroughEmbed", + "app.bsky.feed.defs#interactionSeen", + "app.bsky.feed.defs#interactionLike", + "app.bsky.feed.defs#interactionRepost", + "app.bsky.feed.defs#interactionReply", + "app.bsky.feed.defs#interactionQuote", + "app.bsky.feed.defs#interactionShare" + ] + }, + "feedContext": { + "type": "string", + "description": "Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.", + "maxLength": 2000 + } + } + }, + "requestLess": { + "type": "token", + "description": "Request that less content like the given feed item be shown in the feed" + }, + "requestMore": { + "type": "token", + "description": "Request that more content like the given feed item be shown in the feed" + }, + "clickthroughItem": { + "type": "token", + "description": "User clicked through to the feed item" + }, + "clickthroughAuthor": { + "type": "token", + "description": "User clicked through to the author of the feed item" + }, + "clickthroughReposter": { + "type": "token", + "description": "User clicked through to the reposter of the feed item" + }, + "clickthroughEmbed": { + "type": "token", + "description": "User clicked through to the embedded content of the feed item" + }, + "interactionSeen": { + "type": "token", + "description": "Feed item was seen by user" + }, + "interactionLike": { + "type": "token", + "description": "User liked the feed item" + }, + "interactionRepost": { + "type": "token", + "description": "User reposted the feed item" + }, + "interactionReply": { + "type": "token", + "description": "User replied to the feed item" + }, + "interactionQuote": { + "type": "token", + "description": "User quoted the feed item" + }, + "interactionShare": { + "type": "token", + "description": "User shared the feed item" } } } diff --git a/lexicons/app/bsky/feed/generator.json b/lexicons/app/bsky/feed/generator.json index d0e361b72cb..1406d542fd1 100644 --- a/lexicons/app/bsky/feed/generator.json +++ b/lexicons/app/bsky/feed/generator.json @@ -30,6 +30,10 @@ "accept": ["image/png", "image/jpeg"], "maxSize": 1000000 }, + "acceptsInteractions": { + "type": "boolean", + "description": "Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions" + }, "labels": { "type": "union", "description": "Self-label values", diff --git a/lexicons/app/bsky/feed/sendInteractions.json b/lexicons/app/bsky/feed/sendInteractions.json new file mode 100644 index 00000000000..c50fbbc0e17 --- /dev/null +++ b/lexicons/app/bsky/feed/sendInteractions.json @@ -0,0 +1,33 @@ +{ + "lexicon": 1, + "id": "app.bsky.feed.sendInteractions", + "defs": { + "main": { + "type": "procedure", + "description": "Send information about interactions with feed items back to the feed generator that served them.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["interactions"], + "properties": { + "interactions": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#interaction" + } + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": {} + } + } + } + } +} diff --git a/packages/api/jest.setup.ts b/packages/api/jest.setup.ts index f0e4aaaf36f..4263aad09f4 100644 --- a/packages/api/jest.setup.ts +++ b/packages/api/jest.setup.ts @@ -5,7 +5,7 @@ expect.extend({ toBeModerationResult( actual: ModerationUI, expected: ModerationTestSuiteResultFlag[] | undefined, - context: string = '', + context = '', stringifiedResult: string | undefined = undefined, _ignoreCause = false, ) { diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index 924244e3ced..689607eb3d8 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -118,6 +118,7 @@ import * as AppBskyFeedLike from './types/app/bsky/feed/like' import * as AppBskyFeedPost from './types/app/bsky/feed/post' import * as AppBskyFeedRepost from './types/app/bsky/feed/repost' import * as AppBskyFeedSearchPosts from './types/app/bsky/feed/searchPosts' +import * as AppBskyFeedSendInteractions from './types/app/bsky/feed/sendInteractions' import * as AppBskyFeedThreadgate from './types/app/bsky/feed/threadgate' import * as AppBskyGraphBlock from './types/app/bsky/graph/block' import * as AppBskyGraphDefs from './types/app/bsky/graph/defs' @@ -277,6 +278,7 @@ export * as AppBskyFeedLike from './types/app/bsky/feed/like' export * as AppBskyFeedPost from './types/app/bsky/feed/post' export * as AppBskyFeedRepost from './types/app/bsky/feed/repost' export * as AppBskyFeedSearchPosts from './types/app/bsky/feed/searchPosts' +export * as AppBskyFeedSendInteractions from './types/app/bsky/feed/sendInteractions' export * as AppBskyFeedThreadgate from './types/app/bsky/feed/threadgate' export * as AppBskyGraphBlock from './types/app/bsky/graph/block' export * as AppBskyGraphDefs from './types/app/bsky/graph/defs' @@ -334,6 +336,20 @@ export const COM_ATPROTO_MODERATION = { DefsReasonOther: 'com.atproto.moderation.defs#reasonOther', DefsReasonAppeal: 'com.atproto.moderation.defs#reasonAppeal', } +export const APP_BSKY_FEED = { + DefsRequestLess: 'app.bsky.feed.defs#requestLess', + DefsRequestMore: 'app.bsky.feed.defs#requestMore', + DefsClickthroughItem: 'app.bsky.feed.defs#clickthroughItem', + DefsClickthroughAuthor: 'app.bsky.feed.defs#clickthroughAuthor', + DefsClickthroughReposter: 'app.bsky.feed.defs#clickthroughReposter', + DefsClickthroughEmbed: 'app.bsky.feed.defs#clickthroughEmbed', + DefsInteractionSeen: 'app.bsky.feed.defs#interactionSeen', + DefsInteractionLike: 'app.bsky.feed.defs#interactionLike', + DefsInteractionRepost: 'app.bsky.feed.defs#interactionRepost', + DefsInteractionReply: 'app.bsky.feed.defs#interactionReply', + DefsInteractionQuote: 'app.bsky.feed.defs#interactionQuote', + DefsInteractionShare: 'app.bsky.feed.defs#interactionShare', +} export const APP_BSKY_GRAPH = { DefsModlist: 'app.bsky.graph.defs#modlist', DefsCuratelist: 'app.bsky.graph.defs#curatelist', @@ -1637,6 +1653,17 @@ export class AppBskyFeedNS { throw AppBskyFeedSearchPosts.toKnownErr(e) }) } + + sendInteractions( + data?: AppBskyFeedSendInteractions.InputSchema, + opts?: AppBskyFeedSendInteractions.CallOptions, + ): Promise { + return this._service.xrpc + .call('app.bsky.feed.sendInteractions', opts?.qp, data, opts) + .catch((e) => { + throw AppBskyFeedSendInteractions.toKnownErr(e) + }) + } } export class GeneratorRecord { diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 5d75355f532..b05f13384a4 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -4757,6 +4757,12 @@ export const schemaDict = { type: 'union', refs: ['lex:app.bsky.feed.defs#reasonRepost'], }, + feedContext: { + type: 'string', + description: + 'Context provided by feed generator that may be passed back alongside interactions.', + maxLength: 2000, + }, }, }, replyRef: { @@ -4913,6 +4919,9 @@ export const schemaDict = { type: 'integer', minimum: 0, }, + acceptsInteractions: { + type: 'boolean', + }, labels: { type: 'array', items: { @@ -4951,6 +4960,12 @@ export const schemaDict = { type: 'union', refs: ['lex:app.bsky.feed.defs#skeletonReasonRepost'], }, + feedContext: { + type: 'string', + description: + 'Context that will be passed through to client and may be passed to feed generator back alongside interactions.', + maxLength: 2000, + }, }, }, skeletonReasonRepost: { @@ -4986,6 +5001,89 @@ export const schemaDict = { }, }, }, + interaction: { + type: 'object', + properties: { + item: { + type: 'string', + format: 'at-uri', + }, + event: { + type: 'string', + knownValues: [ + 'app.bsky.feed.defs#requestLess', + 'app.bsky.feed.defs#requestMore', + 'app.bsky.feed.defs#clickthroughItem', + 'app.bsky.feed.defs#clickthroughAuthor', + 'app.bsky.feed.defs#clickthroughReposter', + 'app.bsky.feed.defs#clickthroughEmbed', + 'app.bsky.feed.defs#interactionSeen', + 'app.bsky.feed.defs#interactionLike', + 'app.bsky.feed.defs#interactionRepost', + 'app.bsky.feed.defs#interactionReply', + 'app.bsky.feed.defs#interactionQuote', + 'app.bsky.feed.defs#interactionShare', + ], + }, + feedContext: { + type: 'string', + description: + 'Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.', + maxLength: 2000, + }, + }, + }, + requestLess: { + type: 'token', + description: + 'Request that less content like the given feed item be shown in the feed', + }, + requestMore: { + type: 'token', + description: + 'Request that more content like the given feed item be shown in the feed', + }, + clickthroughItem: { + type: 'token', + description: 'User clicked through to the feed item', + }, + clickthroughAuthor: { + type: 'token', + description: 'User clicked through to the author of the feed item', + }, + clickthroughReposter: { + type: 'token', + description: 'User clicked through to the reposter of the feed item', + }, + clickthroughEmbed: { + type: 'token', + description: + 'User clicked through to the embedded content of the feed item', + }, + interactionSeen: { + type: 'token', + description: 'Feed item was seen by user', + }, + interactionLike: { + type: 'token', + description: 'User liked the feed item', + }, + interactionRepost: { + type: 'token', + description: 'User reposted the feed item', + }, + interactionReply: { + type: 'token', + description: 'User replied to the feed item', + }, + interactionQuote: { + type: 'token', + description: 'User quoted the feed item', + }, + interactionShare: { + type: 'token', + description: 'User shared the feed item', + }, }, }, AppBskyFeedDescribeFeedGenerator: { @@ -5083,6 +5181,11 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + acceptsInteractions: { + type: 'boolean', + description: + 'Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions', + }, labels: { type: 'union', description: 'Self-label values', @@ -6109,6 +6212,40 @@ export const schemaDict = { }, }, }, + AppBskyFeedSendInteractions: { + lexicon: 1, + id: 'app.bsky.feed.sendInteractions', + defs: { + main: { + type: 'procedure', + description: + 'Send information about interactions with feed items back to the feed generator that served them.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['interactions'], + properties: { + interactions: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#interaction', + }, + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: {}, + }, + }, + }, + }, + }, AppBskyFeedThreadgate: { lexicon: 1, id: 'app.bsky.feed.threadgate', @@ -9327,6 +9464,7 @@ export const ids = { AppBskyFeedPost: 'app.bsky.feed.post', AppBskyFeedRepost: 'app.bsky.feed.repost', AppBskyFeedSearchPosts: 'app.bsky.feed.searchPosts', + AppBskyFeedSendInteractions: 'app.bsky.feed.sendInteractions', AppBskyFeedThreadgate: 'app.bsky.feed.threadgate', AppBskyGraphBlock: 'app.bsky.graph.block', AppBskyGraphDefs: 'app.bsky.graph.defs', diff --git a/packages/api/src/client/types/app/bsky/feed/defs.ts b/packages/api/src/client/types/app/bsky/feed/defs.ts index 856d5356086..0bf130447ea 100644 --- a/packages/api/src/client/types/app/bsky/feed/defs.ts +++ b/packages/api/src/client/types/app/bsky/feed/defs.ts @@ -69,6 +69,8 @@ export interface FeedViewPost { post: PostView reply?: ReplyRef reason?: ReasonRepost | { $type: string; [k: string]: unknown } + /** Context provided by feed generator that may be passed back alongside interactions. */ + feedContext?: string [k: string]: unknown } @@ -219,6 +221,7 @@ export interface GeneratorView { descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: string likeCount?: number + acceptsInteractions?: boolean labels?: ComAtprotoLabelDefs.Label[] viewer?: GeneratorViewerState indexedAt: string @@ -257,6 +260,8 @@ export function validateGeneratorViewerState(v: unknown): ValidationResult { export interface SkeletonFeedPost { post: string reason?: SkeletonReasonRepost | { $type: string; [k: string]: unknown } + /** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */ + feedContext?: string [k: string]: unknown } @@ -308,3 +313,61 @@ export function isThreadgateView(v: unknown): v is ThreadgateView { export function validateThreadgateView(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#threadgateView', v) } + +export interface Interaction { + item?: string + event?: + | 'app.bsky.feed.defs#requestLess' + | 'app.bsky.feed.defs#requestMore' + | 'app.bsky.feed.defs#clickthroughItem' + | 'app.bsky.feed.defs#clickthroughAuthor' + | 'app.bsky.feed.defs#clickthroughReposter' + | 'app.bsky.feed.defs#clickthroughEmbed' + | 'app.bsky.feed.defs#interactionSeen' + | 'app.bsky.feed.defs#interactionLike' + | 'app.bsky.feed.defs#interactionRepost' + | 'app.bsky.feed.defs#interactionReply' + | 'app.bsky.feed.defs#interactionQuote' + | 'app.bsky.feed.defs#interactionShare' + | (string & {}) + /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */ + feedContext?: string + [k: string]: unknown +} + +export function isInteraction(v: unknown): v is Interaction { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.defs#interaction' + ) +} + +export function validateInteraction(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#interaction', v) +} + +/** Request that less content like the given feed item be shown in the feed */ +export const REQUESTLESS = 'app.bsky.feed.defs#requestLess' +/** Request that more content like the given feed item be shown in the feed */ +export const REQUESTMORE = 'app.bsky.feed.defs#requestMore' +/** User clicked through to the feed item */ +export const CLICKTHROUGHITEM = 'app.bsky.feed.defs#clickthroughItem' +/** User clicked through to the author of the feed item */ +export const CLICKTHROUGHAUTHOR = 'app.bsky.feed.defs#clickthroughAuthor' +/** User clicked through to the reposter of the feed item */ +export const CLICKTHROUGHREPOSTER = 'app.bsky.feed.defs#clickthroughReposter' +/** User clicked through to the embedded content of the feed item */ +export const CLICKTHROUGHEMBED = 'app.bsky.feed.defs#clickthroughEmbed' +/** Feed item was seen by user */ +export const INTERACTIONSEEN = 'app.bsky.feed.defs#interactionSeen' +/** User liked the feed item */ +export const INTERACTIONLIKE = 'app.bsky.feed.defs#interactionLike' +/** User reposted the feed item */ +export const INTERACTIONREPOST = 'app.bsky.feed.defs#interactionRepost' +/** User replied to the feed item */ +export const INTERACTIONREPLY = 'app.bsky.feed.defs#interactionReply' +/** User quoted the feed item */ +export const INTERACTIONQUOTE = 'app.bsky.feed.defs#interactionQuote' +/** User shared the feed item */ +export const INTERACTIONSHARE = 'app.bsky.feed.defs#interactionShare' diff --git a/packages/api/src/client/types/app/bsky/feed/generator.ts b/packages/api/src/client/types/app/bsky/feed/generator.ts index d9df6464f94..b36796dd353 100644 --- a/packages/api/src/client/types/app/bsky/feed/generator.ts +++ b/packages/api/src/client/types/app/bsky/feed/generator.ts @@ -14,6 +14,8 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + /** Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions */ + acceptsInteractions?: boolean labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts b/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts new file mode 100644 index 00000000000..63ea0cc6a82 --- /dev/null +++ b/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts @@ -0,0 +1,38 @@ +/** + * 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 AppBskyFeedDefs from './defs' + +export interface QueryParams {} + +export interface InputSchema { + interactions: AppBskyFeedDefs.Interaction[] + [k: string]: unknown +} + +export interface OutputSchema { + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers + qp?: QueryParams + encoding: 'application/json' +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/moderation/ui.ts b/packages/api/src/moderation/ui.ts index 0bb5d7464f3..4de1818f659 100644 --- a/packages/api/src/moderation/ui.ts +++ b/packages/api/src/moderation/ui.ts @@ -1,7 +1,7 @@ import { ModerationCause } from './types' export class ModerationUI { - noOverride: boolean = false + noOverride = false filters: ModerationCause[] = [] blurs: ModerationCause[] = [] alerts: ModerationCause[] = [] diff --git a/packages/api/tests/util/moderation-behavior.ts b/packages/api/tests/util/moderation-behavior.ts index 260753b0ab0..9cc8a9a0761 100644 --- a/packages/api/tests/util/moderation-behavior.ts +++ b/packages/api/tests/util/moderation-behavior.ts @@ -63,7 +63,7 @@ expect.extend({ toBeModerationResult( actual: ModerationUI, expected: ModerationTestSuiteResultFlag[] | undefined, - context: string = '', + context = '', stringifiedResult: string | undefined = undefined, _ignoreCause = false, ) { diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index d44cb9b4656..cca3d63041d 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -104,6 +104,7 @@ import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy' import * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds' import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline' import * as AppBskyFeedSearchPosts from './types/app/bsky/feed/searchPosts' +import * as AppBskyFeedSendInteractions from './types/app/bsky/feed/sendInteractions' import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' @@ -137,6 +138,20 @@ export const COM_ATPROTO_MODERATION = { DefsReasonOther: 'com.atproto.moderation.defs#reasonOther', DefsReasonAppeal: 'com.atproto.moderation.defs#reasonAppeal', } +export const APP_BSKY_FEED = { + DefsRequestLess: 'app.bsky.feed.defs#requestLess', + DefsRequestMore: 'app.bsky.feed.defs#requestMore', + DefsClickthroughItem: 'app.bsky.feed.defs#clickthroughItem', + DefsClickthroughAuthor: 'app.bsky.feed.defs#clickthroughAuthor', + DefsClickthroughReposter: 'app.bsky.feed.defs#clickthroughReposter', + DefsClickthroughEmbed: 'app.bsky.feed.defs#clickthroughEmbed', + DefsInteractionSeen: 'app.bsky.feed.defs#interactionSeen', + DefsInteractionLike: 'app.bsky.feed.defs#interactionLike', + DefsInteractionRepost: 'app.bsky.feed.defs#interactionRepost', + DefsInteractionReply: 'app.bsky.feed.defs#interactionReply', + DefsInteractionQuote: 'app.bsky.feed.defs#interactionQuote', + DefsInteractionShare: 'app.bsky.feed.defs#interactionShare', +} export const APP_BSKY_GRAPH = { DefsModlist: 'app.bsky.graph.defs#modlist', DefsCuratelist: 'app.bsky.graph.defs#curatelist', @@ -1361,6 +1376,17 @@ export class AppBskyFeedNS { const nsid = 'app.bsky.feed.searchPosts' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + sendInteractions( + cfg: ConfigOf< + AV, + AppBskyFeedSendInteractions.Handler>, + AppBskyFeedSendInteractions.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.feed.sendInteractions' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } export class AppBskyGraphNS { diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 87e73b19d11..ecaf8f4c939 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -4757,6 +4757,12 @@ export const schemaDict = { type: 'union', refs: ['lex:app.bsky.feed.defs#reasonRepost'], }, + feedContext: { + type: 'string', + description: + 'Context provided by feed generator that may be passed back alongside interactions.', + maxLength: 2000, + }, }, }, replyRef: { @@ -4913,6 +4919,9 @@ export const schemaDict = { type: 'integer', minimum: 0, }, + acceptsInteractions: { + type: 'boolean', + }, labels: { type: 'array', items: { @@ -4951,6 +4960,12 @@ export const schemaDict = { type: 'union', refs: ['lex:app.bsky.feed.defs#skeletonReasonRepost'], }, + feedContext: { + type: 'string', + description: + 'Context that will be passed through to client and may be passed to feed generator back alongside interactions.', + maxLength: 2000, + }, }, }, skeletonReasonRepost: { @@ -4986,6 +5001,89 @@ export const schemaDict = { }, }, }, + interaction: { + type: 'object', + properties: { + item: { + type: 'string', + format: 'at-uri', + }, + event: { + type: 'string', + knownValues: [ + 'app.bsky.feed.defs#requestLess', + 'app.bsky.feed.defs#requestMore', + 'app.bsky.feed.defs#clickthroughItem', + 'app.bsky.feed.defs#clickthroughAuthor', + 'app.bsky.feed.defs#clickthroughReposter', + 'app.bsky.feed.defs#clickthroughEmbed', + 'app.bsky.feed.defs#interactionSeen', + 'app.bsky.feed.defs#interactionLike', + 'app.bsky.feed.defs#interactionRepost', + 'app.bsky.feed.defs#interactionReply', + 'app.bsky.feed.defs#interactionQuote', + 'app.bsky.feed.defs#interactionShare', + ], + }, + feedContext: { + type: 'string', + description: + 'Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.', + maxLength: 2000, + }, + }, + }, + requestLess: { + type: 'token', + description: + 'Request that less content like the given feed item be shown in the feed', + }, + requestMore: { + type: 'token', + description: + 'Request that more content like the given feed item be shown in the feed', + }, + clickthroughItem: { + type: 'token', + description: 'User clicked through to the feed item', + }, + clickthroughAuthor: { + type: 'token', + description: 'User clicked through to the author of the feed item', + }, + clickthroughReposter: { + type: 'token', + description: 'User clicked through to the reposter of the feed item', + }, + clickthroughEmbed: { + type: 'token', + description: + 'User clicked through to the embedded content of the feed item', + }, + interactionSeen: { + type: 'token', + description: 'Feed item was seen by user', + }, + interactionLike: { + type: 'token', + description: 'User liked the feed item', + }, + interactionRepost: { + type: 'token', + description: 'User reposted the feed item', + }, + interactionReply: { + type: 'token', + description: 'User replied to the feed item', + }, + interactionQuote: { + type: 'token', + description: 'User quoted the feed item', + }, + interactionShare: { + type: 'token', + description: 'User shared the feed item', + }, }, }, AppBskyFeedDescribeFeedGenerator: { @@ -5083,6 +5181,11 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + acceptsInteractions: { + type: 'boolean', + description: + 'Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions', + }, labels: { type: 'union', description: 'Self-label values', @@ -6109,6 +6212,40 @@ export const schemaDict = { }, }, }, + AppBskyFeedSendInteractions: { + lexicon: 1, + id: 'app.bsky.feed.sendInteractions', + defs: { + main: { + type: 'procedure', + description: + 'Send information about interactions with feed items back to the feed generator that served them.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['interactions'], + properties: { + interactions: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#interaction', + }, + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: {}, + }, + }, + }, + }, + }, AppBskyFeedThreadgate: { lexicon: 1, id: 'app.bsky.feed.threadgate', @@ -7950,6 +8087,7 @@ export const ids = { AppBskyFeedPost: 'app.bsky.feed.post', AppBskyFeedRepost: 'app.bsky.feed.repost', AppBskyFeedSearchPosts: 'app.bsky.feed.searchPosts', + AppBskyFeedSendInteractions: 'app.bsky.feed.sendInteractions', AppBskyFeedThreadgate: 'app.bsky.feed.threadgate', AppBskyGraphBlock: 'app.bsky.graph.block', AppBskyGraphDefs: 'app.bsky.graph.defs', diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts index 8c99f64f32e..c9395b8c3dd 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts @@ -69,6 +69,8 @@ export interface FeedViewPost { post: PostView reply?: ReplyRef reason?: ReasonRepost | { $type: string; [k: string]: unknown } + /** Context provided by feed generator that may be passed back alongside interactions. */ + feedContext?: string [k: string]: unknown } @@ -219,6 +221,7 @@ export interface GeneratorView { descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: string likeCount?: number + acceptsInteractions?: boolean labels?: ComAtprotoLabelDefs.Label[] viewer?: GeneratorViewerState indexedAt: string @@ -257,6 +260,8 @@ export function validateGeneratorViewerState(v: unknown): ValidationResult { export interface SkeletonFeedPost { post: string reason?: SkeletonReasonRepost | { $type: string; [k: string]: unknown } + /** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */ + feedContext?: string [k: string]: unknown } @@ -308,3 +313,61 @@ export function isThreadgateView(v: unknown): v is ThreadgateView { export function validateThreadgateView(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#threadgateView', v) } + +export interface Interaction { + item?: string + event?: + | 'app.bsky.feed.defs#requestLess' + | 'app.bsky.feed.defs#requestMore' + | 'app.bsky.feed.defs#clickthroughItem' + | 'app.bsky.feed.defs#clickthroughAuthor' + | 'app.bsky.feed.defs#clickthroughReposter' + | 'app.bsky.feed.defs#clickthroughEmbed' + | 'app.bsky.feed.defs#interactionSeen' + | 'app.bsky.feed.defs#interactionLike' + | 'app.bsky.feed.defs#interactionRepost' + | 'app.bsky.feed.defs#interactionReply' + | 'app.bsky.feed.defs#interactionQuote' + | 'app.bsky.feed.defs#interactionShare' + | (string & {}) + /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */ + feedContext?: string + [k: string]: unknown +} + +export function isInteraction(v: unknown): v is Interaction { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.defs#interaction' + ) +} + +export function validateInteraction(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#interaction', v) +} + +/** Request that less content like the given feed item be shown in the feed */ +export const REQUESTLESS = 'app.bsky.feed.defs#requestLess' +/** Request that more content like the given feed item be shown in the feed */ +export const REQUESTMORE = 'app.bsky.feed.defs#requestMore' +/** User clicked through to the feed item */ +export const CLICKTHROUGHITEM = 'app.bsky.feed.defs#clickthroughItem' +/** User clicked through to the author of the feed item */ +export const CLICKTHROUGHAUTHOR = 'app.bsky.feed.defs#clickthroughAuthor' +/** User clicked through to the reposter of the feed item */ +export const CLICKTHROUGHREPOSTER = 'app.bsky.feed.defs#clickthroughReposter' +/** User clicked through to the embedded content of the feed item */ +export const CLICKTHROUGHEMBED = 'app.bsky.feed.defs#clickthroughEmbed' +/** Feed item was seen by user */ +export const INTERACTIONSEEN = 'app.bsky.feed.defs#interactionSeen' +/** User liked the feed item */ +export const INTERACTIONLIKE = 'app.bsky.feed.defs#interactionLike' +/** User reposted the feed item */ +export const INTERACTIONREPOST = 'app.bsky.feed.defs#interactionRepost' +/** User replied to the feed item */ +export const INTERACTIONREPLY = 'app.bsky.feed.defs#interactionReply' +/** User quoted the feed item */ +export const INTERACTIONQUOTE = 'app.bsky.feed.defs#interactionQuote' +/** User shared the feed item */ +export const INTERACTIONSHARE = 'app.bsky.feed.defs#interactionShare' diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/generator.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/generator.ts index 757e74db845..eb8e8d0f633 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/generator.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/generator.ts @@ -14,6 +14,8 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + /** Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions */ + acceptsInteractions?: boolean labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/sendInteractions.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/sendInteractions.ts new file mode 100644 index 00000000000..c22fc1cb2fb --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/sendInteractions.ts @@ -0,0 +1,49 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams {} + +export interface InputSchema { + interactions: AppBskyFeedDefs.Interaction[] + [k: string]: unknown +} + +export interface OutputSchema { + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/index.ts b/packages/ozone/src/lexicon/index.ts index 0698a7608be..f510880f979 100644 --- a/packages/ozone/src/lexicon/index.ts +++ b/packages/ozone/src/lexicon/index.ts @@ -104,6 +104,7 @@ import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy' import * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds' import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline' import * as AppBskyFeedSearchPosts from './types/app/bsky/feed/searchPosts' +import * as AppBskyFeedSendInteractions from './types/app/bsky/feed/sendInteractions' import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' @@ -148,6 +149,20 @@ export const COM_ATPROTO_MODERATION = { DefsReasonOther: 'com.atproto.moderation.defs#reasonOther', DefsReasonAppeal: 'com.atproto.moderation.defs#reasonAppeal', } +export const APP_BSKY_FEED = { + DefsRequestLess: 'app.bsky.feed.defs#requestLess', + DefsRequestMore: 'app.bsky.feed.defs#requestMore', + DefsClickthroughItem: 'app.bsky.feed.defs#clickthroughItem', + DefsClickthroughAuthor: 'app.bsky.feed.defs#clickthroughAuthor', + DefsClickthroughReposter: 'app.bsky.feed.defs#clickthroughReposter', + DefsClickthroughEmbed: 'app.bsky.feed.defs#clickthroughEmbed', + DefsInteractionSeen: 'app.bsky.feed.defs#interactionSeen', + DefsInteractionLike: 'app.bsky.feed.defs#interactionLike', + DefsInteractionRepost: 'app.bsky.feed.defs#interactionRepost', + DefsInteractionReply: 'app.bsky.feed.defs#interactionReply', + DefsInteractionQuote: 'app.bsky.feed.defs#interactionQuote', + DefsInteractionShare: 'app.bsky.feed.defs#interactionShare', +} export const APP_BSKY_GRAPH = { DefsModlist: 'app.bsky.graph.defs#modlist', DefsCuratelist: 'app.bsky.graph.defs#curatelist', @@ -1380,6 +1395,17 @@ export class AppBskyFeedNS { const nsid = 'app.bsky.feed.searchPosts' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + sendInteractions( + cfg: ConfigOf< + AV, + AppBskyFeedSendInteractions.Handler>, + AppBskyFeedSendInteractions.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.feed.sendInteractions' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } export class AppBskyGraphNS { diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 5d75355f532..b05f13384a4 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -4757,6 +4757,12 @@ export const schemaDict = { type: 'union', refs: ['lex:app.bsky.feed.defs#reasonRepost'], }, + feedContext: { + type: 'string', + description: + 'Context provided by feed generator that may be passed back alongside interactions.', + maxLength: 2000, + }, }, }, replyRef: { @@ -4913,6 +4919,9 @@ export const schemaDict = { type: 'integer', minimum: 0, }, + acceptsInteractions: { + type: 'boolean', + }, labels: { type: 'array', items: { @@ -4951,6 +4960,12 @@ export const schemaDict = { type: 'union', refs: ['lex:app.bsky.feed.defs#skeletonReasonRepost'], }, + feedContext: { + type: 'string', + description: + 'Context that will be passed through to client and may be passed to feed generator back alongside interactions.', + maxLength: 2000, + }, }, }, skeletonReasonRepost: { @@ -4986,6 +5001,89 @@ export const schemaDict = { }, }, }, + interaction: { + type: 'object', + properties: { + item: { + type: 'string', + format: 'at-uri', + }, + event: { + type: 'string', + knownValues: [ + 'app.bsky.feed.defs#requestLess', + 'app.bsky.feed.defs#requestMore', + 'app.bsky.feed.defs#clickthroughItem', + 'app.bsky.feed.defs#clickthroughAuthor', + 'app.bsky.feed.defs#clickthroughReposter', + 'app.bsky.feed.defs#clickthroughEmbed', + 'app.bsky.feed.defs#interactionSeen', + 'app.bsky.feed.defs#interactionLike', + 'app.bsky.feed.defs#interactionRepost', + 'app.bsky.feed.defs#interactionReply', + 'app.bsky.feed.defs#interactionQuote', + 'app.bsky.feed.defs#interactionShare', + ], + }, + feedContext: { + type: 'string', + description: + 'Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.', + maxLength: 2000, + }, + }, + }, + requestLess: { + type: 'token', + description: + 'Request that less content like the given feed item be shown in the feed', + }, + requestMore: { + type: 'token', + description: + 'Request that more content like the given feed item be shown in the feed', + }, + clickthroughItem: { + type: 'token', + description: 'User clicked through to the feed item', + }, + clickthroughAuthor: { + type: 'token', + description: 'User clicked through to the author of the feed item', + }, + clickthroughReposter: { + type: 'token', + description: 'User clicked through to the reposter of the feed item', + }, + clickthroughEmbed: { + type: 'token', + description: + 'User clicked through to the embedded content of the feed item', + }, + interactionSeen: { + type: 'token', + description: 'Feed item was seen by user', + }, + interactionLike: { + type: 'token', + description: 'User liked the feed item', + }, + interactionRepost: { + type: 'token', + description: 'User reposted the feed item', + }, + interactionReply: { + type: 'token', + description: 'User replied to the feed item', + }, + interactionQuote: { + type: 'token', + description: 'User quoted the feed item', + }, + interactionShare: { + type: 'token', + description: 'User shared the feed item', + }, }, }, AppBskyFeedDescribeFeedGenerator: { @@ -5083,6 +5181,11 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + acceptsInteractions: { + type: 'boolean', + description: + 'Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions', + }, labels: { type: 'union', description: 'Self-label values', @@ -6109,6 +6212,40 @@ export const schemaDict = { }, }, }, + AppBskyFeedSendInteractions: { + lexicon: 1, + id: 'app.bsky.feed.sendInteractions', + defs: { + main: { + type: 'procedure', + description: + 'Send information about interactions with feed items back to the feed generator that served them.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['interactions'], + properties: { + interactions: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#interaction', + }, + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: {}, + }, + }, + }, + }, + }, AppBskyFeedThreadgate: { lexicon: 1, id: 'app.bsky.feed.threadgate', @@ -9327,6 +9464,7 @@ export const ids = { AppBskyFeedPost: 'app.bsky.feed.post', AppBskyFeedRepost: 'app.bsky.feed.repost', AppBskyFeedSearchPosts: 'app.bsky.feed.searchPosts', + AppBskyFeedSendInteractions: 'app.bsky.feed.sendInteractions', AppBskyFeedThreadgate: 'app.bsky.feed.threadgate', AppBskyGraphBlock: 'app.bsky.graph.block', AppBskyGraphDefs: 'app.bsky.graph.defs', diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts index 8c99f64f32e..c9395b8c3dd 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts @@ -69,6 +69,8 @@ export interface FeedViewPost { post: PostView reply?: ReplyRef reason?: ReasonRepost | { $type: string; [k: string]: unknown } + /** Context provided by feed generator that may be passed back alongside interactions. */ + feedContext?: string [k: string]: unknown } @@ -219,6 +221,7 @@ export interface GeneratorView { descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: string likeCount?: number + acceptsInteractions?: boolean labels?: ComAtprotoLabelDefs.Label[] viewer?: GeneratorViewerState indexedAt: string @@ -257,6 +260,8 @@ export function validateGeneratorViewerState(v: unknown): ValidationResult { export interface SkeletonFeedPost { post: string reason?: SkeletonReasonRepost | { $type: string; [k: string]: unknown } + /** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */ + feedContext?: string [k: string]: unknown } @@ -308,3 +313,61 @@ export function isThreadgateView(v: unknown): v is ThreadgateView { export function validateThreadgateView(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#threadgateView', v) } + +export interface Interaction { + item?: string + event?: + | 'app.bsky.feed.defs#requestLess' + | 'app.bsky.feed.defs#requestMore' + | 'app.bsky.feed.defs#clickthroughItem' + | 'app.bsky.feed.defs#clickthroughAuthor' + | 'app.bsky.feed.defs#clickthroughReposter' + | 'app.bsky.feed.defs#clickthroughEmbed' + | 'app.bsky.feed.defs#interactionSeen' + | 'app.bsky.feed.defs#interactionLike' + | 'app.bsky.feed.defs#interactionRepost' + | 'app.bsky.feed.defs#interactionReply' + | 'app.bsky.feed.defs#interactionQuote' + | 'app.bsky.feed.defs#interactionShare' + | (string & {}) + /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */ + feedContext?: string + [k: string]: unknown +} + +export function isInteraction(v: unknown): v is Interaction { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.defs#interaction' + ) +} + +export function validateInteraction(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#interaction', v) +} + +/** Request that less content like the given feed item be shown in the feed */ +export const REQUESTLESS = 'app.bsky.feed.defs#requestLess' +/** Request that more content like the given feed item be shown in the feed */ +export const REQUESTMORE = 'app.bsky.feed.defs#requestMore' +/** User clicked through to the feed item */ +export const CLICKTHROUGHITEM = 'app.bsky.feed.defs#clickthroughItem' +/** User clicked through to the author of the feed item */ +export const CLICKTHROUGHAUTHOR = 'app.bsky.feed.defs#clickthroughAuthor' +/** User clicked through to the reposter of the feed item */ +export const CLICKTHROUGHREPOSTER = 'app.bsky.feed.defs#clickthroughReposter' +/** User clicked through to the embedded content of the feed item */ +export const CLICKTHROUGHEMBED = 'app.bsky.feed.defs#clickthroughEmbed' +/** Feed item was seen by user */ +export const INTERACTIONSEEN = 'app.bsky.feed.defs#interactionSeen' +/** User liked the feed item */ +export const INTERACTIONLIKE = 'app.bsky.feed.defs#interactionLike' +/** User reposted the feed item */ +export const INTERACTIONREPOST = 'app.bsky.feed.defs#interactionRepost' +/** User replied to the feed item */ +export const INTERACTIONREPLY = 'app.bsky.feed.defs#interactionReply' +/** User quoted the feed item */ +export const INTERACTIONQUOTE = 'app.bsky.feed.defs#interactionQuote' +/** User shared the feed item */ +export const INTERACTIONSHARE = 'app.bsky.feed.defs#interactionShare' diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/generator.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/generator.ts index 757e74db845..eb8e8d0f633 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/generator.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/generator.ts @@ -14,6 +14,8 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + /** Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions */ + acceptsInteractions?: boolean labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/sendInteractions.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/sendInteractions.ts new file mode 100644 index 00000000000..c22fc1cb2fb --- /dev/null +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/sendInteractions.ts @@ -0,0 +1,49 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams {} + +export interface InputSchema { + interactions: AppBskyFeedDefs.Interaction[] + [k: string]: unknown +} + +export interface OutputSchema { + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index 0698a7608be..f510880f979 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -104,6 +104,7 @@ import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy' import * as AppBskyFeedGetSuggestedFeeds from './types/app/bsky/feed/getSuggestedFeeds' import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline' import * as AppBskyFeedSearchPosts from './types/app/bsky/feed/searchPosts' +import * as AppBskyFeedSendInteractions from './types/app/bsky/feed/sendInteractions' import * as AppBskyGraphGetBlocks from './types/app/bsky/graph/getBlocks' import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers' import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows' @@ -148,6 +149,20 @@ export const COM_ATPROTO_MODERATION = { DefsReasonOther: 'com.atproto.moderation.defs#reasonOther', DefsReasonAppeal: 'com.atproto.moderation.defs#reasonAppeal', } +export const APP_BSKY_FEED = { + DefsRequestLess: 'app.bsky.feed.defs#requestLess', + DefsRequestMore: 'app.bsky.feed.defs#requestMore', + DefsClickthroughItem: 'app.bsky.feed.defs#clickthroughItem', + DefsClickthroughAuthor: 'app.bsky.feed.defs#clickthroughAuthor', + DefsClickthroughReposter: 'app.bsky.feed.defs#clickthroughReposter', + DefsClickthroughEmbed: 'app.bsky.feed.defs#clickthroughEmbed', + DefsInteractionSeen: 'app.bsky.feed.defs#interactionSeen', + DefsInteractionLike: 'app.bsky.feed.defs#interactionLike', + DefsInteractionRepost: 'app.bsky.feed.defs#interactionRepost', + DefsInteractionReply: 'app.bsky.feed.defs#interactionReply', + DefsInteractionQuote: 'app.bsky.feed.defs#interactionQuote', + DefsInteractionShare: 'app.bsky.feed.defs#interactionShare', +} export const APP_BSKY_GRAPH = { DefsModlist: 'app.bsky.graph.defs#modlist', DefsCuratelist: 'app.bsky.graph.defs#curatelist', @@ -1380,6 +1395,17 @@ export class AppBskyFeedNS { const nsid = 'app.bsky.feed.searchPosts' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + sendInteractions( + cfg: ConfigOf< + AV, + AppBskyFeedSendInteractions.Handler>, + AppBskyFeedSendInteractions.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.feed.sendInteractions' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } export class AppBskyGraphNS { diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 5d75355f532..b05f13384a4 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -4757,6 +4757,12 @@ export const schemaDict = { type: 'union', refs: ['lex:app.bsky.feed.defs#reasonRepost'], }, + feedContext: { + type: 'string', + description: + 'Context provided by feed generator that may be passed back alongside interactions.', + maxLength: 2000, + }, }, }, replyRef: { @@ -4913,6 +4919,9 @@ export const schemaDict = { type: 'integer', minimum: 0, }, + acceptsInteractions: { + type: 'boolean', + }, labels: { type: 'array', items: { @@ -4951,6 +4960,12 @@ export const schemaDict = { type: 'union', refs: ['lex:app.bsky.feed.defs#skeletonReasonRepost'], }, + feedContext: { + type: 'string', + description: + 'Context that will be passed through to client and may be passed to feed generator back alongside interactions.', + maxLength: 2000, + }, }, }, skeletonReasonRepost: { @@ -4986,6 +5001,89 @@ export const schemaDict = { }, }, }, + interaction: { + type: 'object', + properties: { + item: { + type: 'string', + format: 'at-uri', + }, + event: { + type: 'string', + knownValues: [ + 'app.bsky.feed.defs#requestLess', + 'app.bsky.feed.defs#requestMore', + 'app.bsky.feed.defs#clickthroughItem', + 'app.bsky.feed.defs#clickthroughAuthor', + 'app.bsky.feed.defs#clickthroughReposter', + 'app.bsky.feed.defs#clickthroughEmbed', + 'app.bsky.feed.defs#interactionSeen', + 'app.bsky.feed.defs#interactionLike', + 'app.bsky.feed.defs#interactionRepost', + 'app.bsky.feed.defs#interactionReply', + 'app.bsky.feed.defs#interactionQuote', + 'app.bsky.feed.defs#interactionShare', + ], + }, + feedContext: { + type: 'string', + description: + 'Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.', + maxLength: 2000, + }, + }, + }, + requestLess: { + type: 'token', + description: + 'Request that less content like the given feed item be shown in the feed', + }, + requestMore: { + type: 'token', + description: + 'Request that more content like the given feed item be shown in the feed', + }, + clickthroughItem: { + type: 'token', + description: 'User clicked through to the feed item', + }, + clickthroughAuthor: { + type: 'token', + description: 'User clicked through to the author of the feed item', + }, + clickthroughReposter: { + type: 'token', + description: 'User clicked through to the reposter of the feed item', + }, + clickthroughEmbed: { + type: 'token', + description: + 'User clicked through to the embedded content of the feed item', + }, + interactionSeen: { + type: 'token', + description: 'Feed item was seen by user', + }, + interactionLike: { + type: 'token', + description: 'User liked the feed item', + }, + interactionRepost: { + type: 'token', + description: 'User reposted the feed item', + }, + interactionReply: { + type: 'token', + description: 'User replied to the feed item', + }, + interactionQuote: { + type: 'token', + description: 'User quoted the feed item', + }, + interactionShare: { + type: 'token', + description: 'User shared the feed item', + }, }, }, AppBskyFeedDescribeFeedGenerator: { @@ -5083,6 +5181,11 @@ export const schemaDict = { accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, + acceptsInteractions: { + type: 'boolean', + description: + 'Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions', + }, labels: { type: 'union', description: 'Self-label values', @@ -6109,6 +6212,40 @@ export const schemaDict = { }, }, }, + AppBskyFeedSendInteractions: { + lexicon: 1, + id: 'app.bsky.feed.sendInteractions', + defs: { + main: { + type: 'procedure', + description: + 'Send information about interactions with feed items back to the feed generator that served them.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['interactions'], + properties: { + interactions: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.feed.defs#interaction', + }, + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: {}, + }, + }, + }, + }, + }, AppBskyFeedThreadgate: { lexicon: 1, id: 'app.bsky.feed.threadgate', @@ -9327,6 +9464,7 @@ export const ids = { AppBskyFeedPost: 'app.bsky.feed.post', AppBskyFeedRepost: 'app.bsky.feed.repost', AppBskyFeedSearchPosts: 'app.bsky.feed.searchPosts', + AppBskyFeedSendInteractions: 'app.bsky.feed.sendInteractions', AppBskyFeedThreadgate: 'app.bsky.feed.threadgate', AppBskyGraphBlock: 'app.bsky.graph.block', AppBskyGraphDefs: 'app.bsky.graph.defs', diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts b/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts index 8c99f64f32e..c9395b8c3dd 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts @@ -69,6 +69,8 @@ export interface FeedViewPost { post: PostView reply?: ReplyRef reason?: ReasonRepost | { $type: string; [k: string]: unknown } + /** Context provided by feed generator that may be passed back alongside interactions. */ + feedContext?: string [k: string]: unknown } @@ -219,6 +221,7 @@ export interface GeneratorView { descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: string likeCount?: number + acceptsInteractions?: boolean labels?: ComAtprotoLabelDefs.Label[] viewer?: GeneratorViewerState indexedAt: string @@ -257,6 +260,8 @@ export function validateGeneratorViewerState(v: unknown): ValidationResult { export interface SkeletonFeedPost { post: string reason?: SkeletonReasonRepost | { $type: string; [k: string]: unknown } + /** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */ + feedContext?: string [k: string]: unknown } @@ -308,3 +313,61 @@ export function isThreadgateView(v: unknown): v is ThreadgateView { export function validateThreadgateView(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#threadgateView', v) } + +export interface Interaction { + item?: string + event?: + | 'app.bsky.feed.defs#requestLess' + | 'app.bsky.feed.defs#requestMore' + | 'app.bsky.feed.defs#clickthroughItem' + | 'app.bsky.feed.defs#clickthroughAuthor' + | 'app.bsky.feed.defs#clickthroughReposter' + | 'app.bsky.feed.defs#clickthroughEmbed' + | 'app.bsky.feed.defs#interactionSeen' + | 'app.bsky.feed.defs#interactionLike' + | 'app.bsky.feed.defs#interactionRepost' + | 'app.bsky.feed.defs#interactionReply' + | 'app.bsky.feed.defs#interactionQuote' + | 'app.bsky.feed.defs#interactionShare' + | (string & {}) + /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */ + feedContext?: string + [k: string]: unknown +} + +export function isInteraction(v: unknown): v is Interaction { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.feed.defs#interaction' + ) +} + +export function validateInteraction(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.feed.defs#interaction', v) +} + +/** Request that less content like the given feed item be shown in the feed */ +export const REQUESTLESS = 'app.bsky.feed.defs#requestLess' +/** Request that more content like the given feed item be shown in the feed */ +export const REQUESTMORE = 'app.bsky.feed.defs#requestMore' +/** User clicked through to the feed item */ +export const CLICKTHROUGHITEM = 'app.bsky.feed.defs#clickthroughItem' +/** User clicked through to the author of the feed item */ +export const CLICKTHROUGHAUTHOR = 'app.bsky.feed.defs#clickthroughAuthor' +/** User clicked through to the reposter of the feed item */ +export const CLICKTHROUGHREPOSTER = 'app.bsky.feed.defs#clickthroughReposter' +/** User clicked through to the embedded content of the feed item */ +export const CLICKTHROUGHEMBED = 'app.bsky.feed.defs#clickthroughEmbed' +/** Feed item was seen by user */ +export const INTERACTIONSEEN = 'app.bsky.feed.defs#interactionSeen' +/** User liked the feed item */ +export const INTERACTIONLIKE = 'app.bsky.feed.defs#interactionLike' +/** User reposted the feed item */ +export const INTERACTIONREPOST = 'app.bsky.feed.defs#interactionRepost' +/** User replied to the feed item */ +export const INTERACTIONREPLY = 'app.bsky.feed.defs#interactionReply' +/** User quoted the feed item */ +export const INTERACTIONQUOTE = 'app.bsky.feed.defs#interactionQuote' +/** User shared the feed item */ +export const INTERACTIONSHARE = 'app.bsky.feed.defs#interactionShare' diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/generator.ts b/packages/pds/src/lexicon/types/app/bsky/feed/generator.ts index 757e74db845..eb8e8d0f633 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/generator.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/generator.ts @@ -14,6 +14,8 @@ export interface Record { description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef + /** Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions */ + acceptsInteractions?: boolean labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/sendInteractions.ts b/packages/pds/src/lexicon/types/app/bsky/feed/sendInteractions.ts new file mode 100644 index 00000000000..c22fc1cb2fb --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/feed/sendInteractions.ts @@ -0,0 +1,49 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' +import * as AppBskyFeedDefs from './defs' + +export interface QueryParams {} + +export interface InputSchema { + interactions: AppBskyFeedDefs.Interaction[] + [k: string]: unknown +} + +export interface OutputSchema { + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput