Skip to content

Commit

Permalink
posts_with_video filter (#3390)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelbsky authored Jan 21, 2025
1 parent 6b8a51e commit c0a75d3
Show file tree
Hide file tree
Showing 19 changed files with 169 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .changeset/real-kids-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@atproto/bsky": patch
"@atproto/api": patch
---

posts_with_video filter in getAuthorFeed
3 changes: 2 additions & 1 deletion lexicons/app/bsky/feed/getAuthorFeed.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"posts_with_replies",
"posts_no_replies",
"posts_with_media",
"posts_and_author_threads"
"posts_and_author_threads",
"posts_with_video"
],
"default": "posts_with_replies"
},
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/client/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6230,6 +6230,7 @@ export const schemaDict = {
'posts_no_replies',
'posts_with_media',
'posts_and_author_threads',
'posts_with_video',
],
default: 'posts_with_replies',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface QueryParams {
| 'posts_no_replies'
| 'posts_with_media'
| 'posts_and_author_threads'
| 'posts_with_video'
| (string & {})
includePins?: boolean
}
Expand Down
3 changes: 3 additions & 0 deletions packages/bsky/proto/bsky.proto
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ message PostRecordMeta {
bool violates_embedding_rules = 4;
bool has_post_gate = 5;
bool has_thread_gate = 6;
bool has_video = 7;
}

message GetPostRecordsRequest {
Expand Down Expand Up @@ -788,6 +789,7 @@ enum FeedType {
FEED_TYPE_POSTS_AND_AUTHOR_THREADS = 1;
FEED_TYPE_POSTS_NO_REPLIES = 2;
FEED_TYPE_POSTS_WITH_MEDIA = 3;
FEED_TYPE_POSTS_WITH_VIDEO = 4;
}

// - Returns recent posts authored by a given DID, paginated
Expand All @@ -811,6 +813,7 @@ message AuthorFeedItem {
bool is_reply = 8;
bool is_repost = 9;
bool is_quote_post = 10;
bool posts_with_video = 11;
}

message GetAuthorFeedResponse {
Expand Down
1 change: 1 addition & 0 deletions packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const FILTER_TO_FEED_TYPE = {
posts_no_replies: FeedType.POSTS_NO_REPLIES,
posts_with_media: FeedType.POSTS_WITH_MEDIA,
posts_and_author_threads: FeedType.POSTS_AND_AUTHOR_THREADS,
posts_with_video: FeedType.POSTS_WITH_VIDEO,
}

export const skeleton = async (inputs: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Kysely } from 'kysely'

export async function up(db: Kysely<unknown>): Promise<void> {
// postEmbedVideo
await db.schema
.createTable('post_embed_video')
.addColumn('postUri', 'varchar', (col) => col.notNull())
.addColumn('videoCid', 'varchar', (col) => col.notNull())
.addColumn('alt', 'varchar')
.addPrimaryKeyConstraint('post_embed_video_pkey', ['postUri'])
.execute()
}

export async function down(db: Kysely<unknown>): Promise<void> {
// postEmbedVideo
await db.schema.dropTable('post_embed_video').execute()
}
1 change: 1 addition & 0 deletions packages/bsky/src/data-plane/server/db/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ export * as _20240808T224251220Z from './20240808T224251220Z-post-gate-flags'
export * as _20240829T211238293Z from './20240829T211238293Z-simplify-actor-sync'
export * as _20240831T134810923Z from './20240831T134810923Z-pinned-posts'
export * as _20241114T153108102Z from './20241114T153108102Z-add-starter-packs-name'
export * as _20250116T222618297Z from './20250116T222618297Z-post-embed-video'
8 changes: 8 additions & 0 deletions packages/bsky/src/data-plane/server/db/tables/post-embed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const imageTableName = 'post_embed_image'
export const externalTableName = 'post_embed_external'
export const recordTableName = 'post_embed_record'
export const videoTableName = 'post_embed_video'

export interface PostEmbedImage {
postUri: string
Expand All @@ -23,8 +24,15 @@ export interface PostEmbedRecord {
embedCid: string
}

export interface PostEmbedVideo {
postUri: string
videoCid: string
alt: string | null
}

export type PartialDB = {
[imageTableName]: PostEmbedImage
[externalTableName]: PostEmbedExternal
[recordTableName]: PostEmbedRecord
[videoTableName]: PostEmbedVideo
}
27 changes: 25 additions & 2 deletions packages/bsky/src/data-plane/server/indexing/plugins/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { isMain as isEmbedImage } from '../../../../lexicon/types/app/bsky/embed
import { isMain as isEmbedExternal } from '../../../../lexicon/types/app/bsky/embed/external'
import { isMain as isEmbedRecord } from '../../../../lexicon/types/app/bsky/embed/record'
import { isMain as isEmbedRecordWithMedia } from '../../../../lexicon/types/app/bsky/embed/recordWithMedia'
import { isMain as isEmbedVideo } from '../../../../lexicon/types/app/bsky/embed/video'
import {
isMention,
isLink,
Expand Down Expand Up @@ -41,6 +42,7 @@ type Post = Selectable<DatabaseSchemaType['post']>
type PostEmbedImage = DatabaseSchemaType['post_embed_image']
type PostEmbedExternal = DatabaseSchemaType['post_embed_external']
type PostEmbedRecord = DatabaseSchemaType['post_embed_record']
type PostEmbedVideo = DatabaseSchemaType['post_embed_video']
type PostAncestor = {
uri: string
height: number
Expand All @@ -55,7 +57,12 @@ type PostDescendent = {
type IndexedPost = {
post: Post
facets?: { type: 'mention' | 'link'; value: string }[]
embeds?: (PostEmbedImage[] | PostEmbedExternal | PostEmbedRecord)[]
embeds?: (
| PostEmbedImage[]
| PostEmbedExternal
| PostEmbedRecord
| PostEmbedVideo
)[]
ancestors?: PostAncestor[]
descendents?: PostDescendent[]
threadgate?: GateRecord
Expand Down Expand Up @@ -149,7 +156,12 @@ const insertFn = async (
return []
})
// Embed indices
const embeds: (PostEmbedImage[] | PostEmbedExternal | PostEmbedRecord)[] = []
const embeds: (
| PostEmbedImage[]
| PostEmbedExternal
| PostEmbedRecord
| PostEmbedVideo
)[] = []
const postEmbeds = separateEmbeds(obj.embed)
for (const postEmbed of postEmbeds) {
if (isEmbedImage(postEmbed)) {
Expand Down Expand Up @@ -232,6 +244,17 @@ const insertFn = async (
.executeTakeFirst()
}
}
} else if (isEmbedVideo(postEmbed)) {
const { video } = postEmbed
const videoEmbed = {
postUri: uri.toString(),
videoCid: video.ref.toString(),
// @NOTE: alt is required for image but not for video on the lexicon.
alt: postEmbed.alt ?? null,
}
embeds.push(videoEmbed)

await db.insertInto('post_embed_video').values(videoEmbed).execute()
}
}

Expand Down
11 changes: 11 additions & 0 deletions packages/bsky/src/data-plane/server/routes/feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
.select('post_embed_image.postUri')
.whereRef('post_embed_image.postUri', '=', 'feed_item.postUri'),
)
} else if (feedType === FeedType.POSTS_WITH_VIDEO) {
builder = builder
// only your own posts
.where('type', '=', 'post')
// only posts with video
.whereExists((qb) =>
qb
.selectFrom('post_embed_video')
.select('post_embed_video.postUri')
.whereRef('post_embed_video.postUri', '=', 'feed_item.postUri'),
)
} else if (feedType === FeedType.POSTS_NO_REPLIES) {
builder = builder.where((qb) =>
qb.where('post.replyParent', 'is', null).orWhere('type', '=', 'repost'),
Expand Down
1 change: 1 addition & 0 deletions packages/bsky/src/lexicon/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6230,6 +6230,7 @@ export const schemaDict = {
'posts_no_replies',
'posts_with_media',
'posts_and_author_threads',
'posts_with_video',
],
default: 'posts_with_replies',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface QueryParams {
| 'posts_no_replies'
| 'posts_with_media'
| 'posts_and_author_threads'
| 'posts_with_video'
| (string & {})
includePins: boolean
}
Expand Down
23 changes: 23 additions & 0 deletions packages/bsky/src/proto/bsky_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@ export enum FeedType {
* @generated from enum value: FEED_TYPE_POSTS_WITH_MEDIA = 3;
*/
POSTS_WITH_MEDIA = 3,

/**
* @generated from enum value: FEED_TYPE_POSTS_WITH_VIDEO = 4;
*/
POSTS_WITH_VIDEO = 4,
}
// Retrieve enum metadata with: proto3.getEnumType(FeedType)
proto3.util.setEnumType(FeedType, 'bsky.FeedType', [
{ no: 0, name: 'FEED_TYPE_UNSPECIFIED' },
{ no: 1, name: 'FEED_TYPE_POSTS_AND_AUTHOR_THREADS' },
{ no: 2, name: 'FEED_TYPE_POSTS_NO_REPLIES' },
{ no: 3, name: 'FEED_TYPE_POSTS_WITH_MEDIA' },
{ no: 4, name: 'FEED_TYPE_POSTS_WITH_VIDEO' },
])

/**
Expand Down Expand Up @@ -975,6 +981,11 @@ export class PostRecordMeta extends Message<PostRecordMeta> {
*/
hasThreadGate = false

/**
* @generated from field: bool has_video = 7;
*/
hasVideo = false

constructor(data?: PartialMessage<PostRecordMeta>) {
super()
proto3.util.initPartial(data, this)
Expand Down Expand Up @@ -1009,6 +1020,7 @@ export class PostRecordMeta extends Message<PostRecordMeta> {
kind: 'scalar',
T: 8 /* ScalarType.BOOL */,
},
{ no: 7, name: 'has_video', kind: 'scalar', T: 8 /* ScalarType.BOOL */ },
])

static fromBinary(
Expand Down Expand Up @@ -8540,6 +8552,11 @@ export class AuthorFeedItem extends Message<AuthorFeedItem> {
*/
isQuotePost = false

/**
* @generated from field: bool posts_with_video = 11;
*/
postsWithVideo = false

constructor(data?: PartialMessage<AuthorFeedItem>) {
super()
proto3.util.initPartial(data, this)
Expand Down Expand Up @@ -8578,6 +8595,12 @@ export class AuthorFeedItem extends Message<AuthorFeedItem> {
kind: 'scalar',
T: 8 /* ScalarType.BOOL */,
},
{
no: 11,
name: 'posts_with_video',
kind: 'scalar',
T: 8 /* ScalarType.BOOL */,
},
])

static fromBinary(
Expand Down
64 changes: 64 additions & 0 deletions packages/bsky/tests/views/author-feed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import {
import { ReplyRef, isRecord } from '../../src/lexicon/types/app/bsky/feed/post'
import { isView as isEmbedRecordWithMedia } from '../../src/lexicon/types/app/bsky/embed/recordWithMedia'
import { isView as isImageEmbed } from '../../src/lexicon/types/app/bsky/embed/images'
import { isView as isVideoEmbed } from '../../src/lexicon/types/app/bsky/embed/video'
import { isPostView } from '../../src/lexicon/types/app/bsky/feed/defs'
import { uriToDid } from '../../src/util/uris'
import { ids } from '../../src/lexicon/lexicons'
import { VideoEmbed } from '../../src/views/types'

describe('pds author feed views', () => {
let network: TestNetwork
Expand Down Expand Up @@ -319,6 +321,68 @@ describe('pds author feed views', () => {
expect(danFeed.feed.length).toEqual(0)
})

it('can filter by posts_with_video', async () => {
const { data: carolFeedBefore } =
await agent.api.app.bsky.feed.getAuthorFeed({
actor: carol,
filter: 'posts_with_video',
})
expect(carolFeedBefore.feed).toHaveLength(0)

const { data: video } = await pdsAgent.api.com.atproto.repo.uploadBlob(
Buffer.from('notarealvideo'),
{
headers: sc.getHeaders(sc.dids.carol),
encoding: 'image/mp4',
},
)

await sc.post(carol, 'video post', undefined, undefined, undefined, {
embed: {
$type: 'app.bsky.embed.video',
video: video.blob,
alt: 'alt text',
aspectRatio: { height: 3, width: 4 },
} satisfies VideoEmbed,
})
await network.processAll()

const { data: carolFeed } = await agent.api.app.bsky.feed.getAuthorFeed({
actor: carol,
filter: 'posts_with_video',
})

expect(carolFeed.feed).toHaveLength(1)
expect(
carolFeed.feed.every(({ post }) => {
const isRecordWithActorMedia =
isEmbedRecordWithMedia(post.embed) && isVideoEmbed(post.embed?.media)
const isActorMedia = isVideoEmbed(post.embed)
const isFromActor = post.author.did === carol

return (isRecordWithActorMedia || isActorMedia) && isFromActor
}),
).toBeTruthy()

const { data: bobFeed } = await agent.api.app.bsky.feed.getAuthorFeed({
actor: bob,
filter: 'posts_with_video',
})

expect(
bobFeed.feed.every(({ post }) => {
return isVideoEmbed(post.embed) && post.author.did === bob
}),
).toBeTruthy()

const { data: danFeed } = await agent.api.app.bsky.feed.getAuthorFeed({
actor: dan,
filter: 'posts_with_video',
})

expect(danFeed.feed.length).toEqual(0)
})

it('filters by posts_no_replies', async () => {
const { data: carolFeed } = await agent.api.app.bsky.feed.getAuthorFeed({
actor: carol,
Expand Down
1 change: 1 addition & 0 deletions packages/ozone/src/lexicon/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6230,6 +6230,7 @@ export const schemaDict = {
'posts_no_replies',
'posts_with_media',
'posts_and_author_threads',
'posts_with_video',
],
default: 'posts_with_replies',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface QueryParams {
| 'posts_no_replies'
| 'posts_with_media'
| 'posts_and_author_threads'
| 'posts_with_video'
| (string & {})
includePins: boolean
}
Expand Down
1 change: 1 addition & 0 deletions packages/pds/src/lexicon/lexicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6230,6 +6230,7 @@ export const schemaDict = {
'posts_no_replies',
'posts_with_media',
'posts_and_author_threads',
'posts_with_video',
],
default: 'posts_with_replies',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface QueryParams {
| 'posts_no_replies'
| 'posts_with_media'
| 'posts_and_author_threads'
| 'posts_with_video'
| (string & {})
includePins: boolean
}
Expand Down

0 comments on commit c0a75d3

Please sign in to comment.