Add ability to embed lists ()

* add cid to ListView and ListViewBasic

* add test for mute list embed

* add support for list embeds views

* test

* port to appview

* update missing snap

---------

Co-authored-by: dholms <dtholmgren@gmail.com>
This commit is contained in:
Ansh 2023-06-23 13:30:52 -07:00 committed by GitHub
parent 3da0324873
commit 0306f81d37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 354 additions and 56 deletions
lexicons/app/bsky
packages
api/src/client
lexicons.ts
types/app/bsky
bsky
src
api/app/bsky/graph
lexicon
lexicons.ts
types/app/bsky
services
tests/views
pds
src
app-view
api/app/bsky/graph
services
lexicon
lexicons.ts
types/app/bsky
tests

@ -20,7 +20,8 @@
"#viewRecord",
"#viewNotFound",
"#viewBlocked",
"app.bsky.feed.defs#generatorView"
"app.bsky.feed.defs#generatorView",
"app.bsky.graph.defs#listView"
]
}
}

@ -4,9 +4,10 @@
"defs": {
"listViewBasic": {
"type": "object",
"required": ["uri", "name", "purpose"],
"required": ["uri", "cid", "name", "purpose"],
"properties": {
"uri": {"type": "string", "format": "at-uri"},
"cid": {"type": "string", "format": "cid"},
"name": {"type": "string", "maxLength": 64, "minLength": 1},
"purpose": {"type": "ref", "ref": "#listPurpose"},
"avatar": {"type": "string"},
@ -16,9 +17,10 @@
},
"listView": {
"type": "object",
"required": ["uri", "creator", "name", "purpose", "indexedAt"],
"required": ["uri", "cid", "creator", "name", "purpose", "indexedAt"],
"properties": {
"uri": {"type": "string", "format": "at-uri"},
"cid": {"type": "string", "format": "cid"},
"creator": {"type": "ref", "ref": "app.bsky.actor.defs#profileView"},
"name": {"type": "string", "maxLength": 64, "minLength": 1},
"purpose": {"type": "ref", "ref": "#listPurpose"},

@ -4136,6 +4136,7 @@ export const schemaDict = {
'lex:app.bsky.embed.record#viewNotFound',
'lex:app.bsky.embed.record#viewBlocked',
'lex:app.bsky.feed.defs#generatorView',
'lex:app.bsky.graph.defs#listView',
],
},
},
@ -5370,12 +5371,16 @@ export const schemaDict = {
defs: {
listViewBasic: {
type: 'object',
required: ['uri', 'name', 'purpose'],
required: ['uri', 'cid', 'name', 'purpose'],
properties: {
uri: {
type: 'string',
format: 'at-uri',
},
cid: {
type: 'string',
format: 'cid',
},
name: {
type: 'string',
maxLength: 64,
@ -5400,12 +5405,16 @@ export const schemaDict = {
},
listView: {
type: 'object',
required: ['uri', 'creator', 'name', 'purpose', 'indexedAt'],
required: ['uri', 'cid', 'creator', 'name', 'purpose', 'indexedAt'],
properties: {
uri: {
type: 'string',
format: 'at-uri',
},
cid: {
type: 'string',
format: 'cid',
},
creator: {
type: 'ref',
ref: 'lex:app.bsky.actor.defs#profileView',

@ -7,6 +7,7 @@ import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
import * as AppBskyFeedDefs from '../feed/defs'
import * as AppBskyGraphDefs from '../graph/defs'
import * as AppBskyActorDefs from '../actor/defs'
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
import * as AppBskyEmbedImages from './images'
@ -37,6 +38,7 @@ export interface View {
| ViewNotFound
| ViewBlocked
| AppBskyFeedDefs.GeneratorView
| AppBskyGraphDefs.ListView
| { $type: string; [k: string]: unknown }
[k: string]: unknown
}

@ -10,6 +10,7 @@ import * as AppBskyRichtextFacet from '../richtext/facet'
export interface ListViewBasic {
uri: string
cid: string
name: string
purpose: ListPurpose
avatar?: string
@ -32,6 +33,7 @@ export function validateListViewBasic(v: unknown): ValidationResult {
export interface ListView {
uri: string
cid: string
creator: AppBskyActorDefs.ProfileView
name: string
purpose: ListPurpose

@ -57,6 +57,7 @@ export default function (server: Server, ctx: AppContext) {
const subject = {
uri: listRes.uri,
cid: listRes.cid,
creator,
name: listRes.name,
purpose: listRes.purpose,

@ -4136,6 +4136,7 @@ export const schemaDict = {
'lex:app.bsky.embed.record#viewNotFound',
'lex:app.bsky.embed.record#viewBlocked',
'lex:app.bsky.feed.defs#generatorView',
'lex:app.bsky.graph.defs#listView',
],
},
},
@ -5370,12 +5371,16 @@ export const schemaDict = {
defs: {
listViewBasic: {
type: 'object',
required: ['uri', 'name', 'purpose'],
required: ['uri', 'cid', 'name', 'purpose'],
properties: {
uri: {
type: 'string',
format: 'at-uri',
},
cid: {
type: 'string',
format: 'cid',
},
name: {
type: 'string',
maxLength: 64,
@ -5400,12 +5405,16 @@ export const schemaDict = {
},
listView: {
type: 'object',
required: ['uri', 'creator', 'name', 'purpose', 'indexedAt'],
required: ['uri', 'cid', 'creator', 'name', 'purpose', 'indexedAt'],
properties: {
uri: {
type: 'string',
format: 'at-uri',
},
cid: {
type: 'string',
format: 'cid',
},
creator: {
type: 'ref',
ref: 'lex:app.bsky.actor.defs#profileView',

@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
import * as AppBskyFeedDefs from '../feed/defs'
import * as AppBskyGraphDefs from '../graph/defs'
import * as AppBskyActorDefs from '../actor/defs'
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
import * as AppBskyEmbedImages from './images'
@ -37,6 +38,7 @@ export interface View {
| ViewNotFound
| ViewBlocked
| AppBskyFeedDefs.GeneratorView
| AppBskyGraphDefs.ListView
| { $type: string; [k: string]: unknown }
[k: string]: unknown
}

@ -10,6 +10,7 @@ import * as AppBskyRichtextFacet from '../richtext/facet'
export interface ListViewBasic {
uri: string
cid: string
name: string
purpose: ListPurpose
avatar?: string
@ -32,6 +33,7 @@ export function validateListViewBasic(v: unknown): ValidationResult {
export interface ListView {
uri: string
cid: string
creator: AppBskyActorDefs.ProfileView
name: string
purpose: ListPurpose

@ -294,6 +294,7 @@ export class ActorViews {
...acc,
[cur.subjectDid]: {
uri: cur.uri,
cid: cur.cid,
name: cur.name,
purpose: cur.purpose,
avatar: cur.avatarCid

@ -23,6 +23,7 @@ import {
} from '../types'
import { LabelService } from '../label'
import { ActorService } from '../actor'
import { GraphService } from '../graph'
import { FeedViews } from './views'
import { FeedGenInfoMap } from './types'
@ -34,6 +35,7 @@ export class FeedService {
services = {
label: LabelService.creator()(this.db),
actor: ActorService.creator(this.imgUriBuilder)(this.db),
graph: GraphService.creator(this.imgUriBuilder)(this.db),
}
static creator(imgUriBuilder: ImageUriBuilder) {
@ -310,17 +312,24 @@ export class FeedService {
const nestedFeedGenUris = nestedUris.filter(
(uri) => new AtUri(uri).collection === ids.AppBskyFeedGenerator,
)
const [postViews, actorViews, deepEmbedViews, labelViews, feedGenViews] =
await Promise.all([
this.getPostViews(nestedPostUris, viewer),
this.getActorViews(nestedDids, viewer, { skipLabels: true }),
this.embedsForPosts(nestedUris, viewer, _depth + 1),
this.services.label.getLabelsForSubjects([
...nestedUris,
...nestedDids,
]),
this.getFeedGeneratorViews(nestedFeedGenUris, viewer),
])
const nestedListUris = nestedUris.filter(
(uri) => new AtUri(uri).collection === ids.AppBskyGraphList,
)
const [
postViews,
actorViews,
deepEmbedViews,
labelViews,
feedGenViews,
listViews,
] = await Promise.all([
this.getPostViews(nestedPostUris, viewer),
this.getActorViews(nestedDids, viewer, { skipLabels: true }),
this.embedsForPosts(nestedUris, viewer, _depth + 1),
this.services.label.getLabelsForSubjects([...nestedUris, ...nestedDids]),
this.getFeedGeneratorViews(nestedFeedGenUris, viewer),
this.services.graph.getListViews(nestedListUris, viewer),
])
let embeds = images.reduce((acc, cur) => {
const embed = (acc[cur.postUri] ??= {
$type: 'app.bsky.embed.images#view',
@ -378,6 +387,16 @@ export class FeedService {
),
},
}
} else if (collection === ids.AppBskyGraphList && listViews[cur.uri]) {
recordEmbed = {
record: {
$type: 'app.bsky.graph.defs#listView',
...this.services.graph.formatListView(
listViews[cur.uri],
actorViews,
),
},
}
} else if (collection === ids.AppBskyFeedPost && postViews[cur.uri]) {
const formatted = this.views.formatPostView(
cur.uri,

@ -173,9 +173,24 @@ export class GraphService {
}
}
async getListViews(listUris: string[], requester: string | null) {
if (listUris.length < 1) return {}
const lists = await this.getListsQb(requester)
.where('list.uri', 'in', listUris)
.execute()
return lists.reduce(
(acc, cur) => ({
...acc,
[cur.uri]: cur,
}),
{},
)
}
formatListView(list: ListInfo, profiles: Record<string, ProfileView>) {
return {
uri: list.uri,
cid: list.cid,
creator: profiles[list.creator],
name: list.name,
purpose: list.purpose,

@ -1,5 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`bsky views with mutes from mute lists embeds lists in posts 1`] = `
Object {
"author": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"did": "user(0)",
"displayName": "ali",
"handle": "alice.test",
"labels": Array [],
"viewer": Object {
"blockedBy": false,
"muted": false,
},
},
"cid": "cids(0)",
"embed": Object {
"$type": "app.bsky.embed.record#view",
"record": Object {
"$type": "app.bsky.graph.defs#listView",
"cid": "cids(3)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"did": "user(0)",
"displayName": "ali",
"handle": "alice.test",
"viewer": Object {
"blockedBy": false,
"muted": false,
},
},
"description": "new descript",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "updated alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
"uri": "record(1)",
"viewer": Object {
"muted": false,
},
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
"createdAt": "1970-01-01T00:00:00.000Z",
"embed": Object {
"$type": "app.bsky.embed.record",
"record": Object {
"cid": "cids(2)",
"uri": "record(1)",
},
},
"text": "list embed!",
},
"replyCount": 0,
"repostCount": 0,
"uri": "record(0)",
"viewer": Object {},
}
`;
exports[`bsky views with mutes from mute lists flags mutes in threads 1`] = `
Object {
"$type": "app.bsky.feed.defs#threadViewPost",
@ -48,6 +109,7 @@ Object {
"muted": true,
"mutedByList": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
@ -98,6 +160,7 @@ Object {
"muted": true,
"mutedByList": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
@ -108,21 +171,21 @@ Object {
},
},
},
"cid": "cids(3)",
"cid": "cids(4)",
"embed": Object {
"$type": "app.bsky.embed.images#view",
"images": Array [
Object {
"alt": "tests/image/fixtures/key-landscape-small.jpg",
"fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(4)@jpeg",
"thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(4)@jpeg",
"fullsize": "https://bsky.public.url/image/sig()/rs:fit:2000:2000:1:0/plain/user(4)/cids(5)@jpeg",
"thumb": "https://bsky.public.url/image/sig()/rs:fit:1000:1000:1:0/plain/user(4)/cids(5)@jpeg",
},
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(3)",
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
@ -130,7 +193,7 @@ Object {
"val": "test-label",
},
Object {
"cid": "cids(3)",
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
@ -151,7 +214,7 @@ Object {
"$type": "blob",
"mimeType": "image/jpeg",
"ref": Object {
"$link": "cids(4)",
"$link": "cids(5)",
},
"size": 4114,
},
@ -185,8 +248,9 @@ Object {
"cursor": "0000000000000::bafycid",
"lists": Array [
Object {
"cid": "cids(0)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"description": "its me!",
"did": "user(0)",
"displayName": "ali",
@ -209,9 +273,10 @@ Object {
},
},
Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"cid": "cids(2)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"description": "its me!",
"did": "user(0)",
"displayName": "ali",
@ -242,8 +307,9 @@ Object {
"cursor": "0000000000000::bafycid",
"lists": Array [
Object {
"cid": "cids(0)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"description": "its me!",
"did": "user(0)",
"displayName": "ali",
@ -266,9 +332,10 @@ Object {
},
},
Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"cid": "cids(2)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"description": "its me!",
"did": "user(0)",
"displayName": "ali",
@ -308,7 +375,8 @@ Object {
"followedBy": "record(1)",
"muted": true,
"mutedByList": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
@ -322,7 +390,7 @@ Object {
},
Object {
"subject": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(3)/cids(1)@jpeg",
"description": "hi im bob label_me",
"did": "user(2)",
"displayName": "bobby",
@ -334,7 +402,8 @@ Object {
"following": "record(2)",
"muted": true,
"mutedByList": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
@ -348,9 +417,10 @@ Object {
},
],
"list": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"cid": "cids(0)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"description": "its me!",
"did": "user(4)",
"displayName": "ali",

@ -1,7 +1,7 @@
import AtpAgent, { AtUri } from '@atproto/api'
import { TestNetwork } from '@atproto/dev-env'
import { forSnapshot } from '../_util'
import { SeedClient } from '../seeds/client'
import { RecordRef, SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('bsky views with mutes from mute lists', () => {
@ -38,6 +38,7 @@ describe('bsky views with mutes from mute lists', () => {
})
let listUri: string
let listCid: string
it('creates a list with some items', async () => {
const avatar = await sc.uploadFile(
@ -58,6 +59,7 @@ describe('bsky views with mutes from mute lists', () => {
sc.getHeaders(alice),
)
listUri = list.uri
listCid = list.cid
await pdsAgent.api.app.bsky.graph.listitem.create(
{ repo: alice },
{
@ -330,4 +332,21 @@ describe('bsky views with mutes from mute lists', () => {
expect(got.data.list.avatar).toBeUndefined()
expect(got.data.items.length).toBe(2)
})
it('embeds lists in posts', async () => {
const postRef = await sc.post(
alice,
'list embed!',
undefined,
undefined,
new RecordRef(listUri, listCid),
)
await network.processAll()
const res = await agent.api.app.bsky.feed.getPosts(
{ uris: [postRef.ref.uriStr] },
{ headers: await network.serviceHeaders(alice) },
)
expect(res.data.posts.length).toBe(1)
expect(forSnapshot(res.data.posts[0])).toMatchSnapshot()
})
})

@ -68,6 +68,7 @@ export default function (server: Server, ctx: AppContext) {
const subject = {
uri: listRes.uri,
cid: listRes.cid,
creator,
name: listRes.name,
purpose: listRes.purpose,

@ -265,6 +265,7 @@ export class ActorViews {
...acc,
[cur.subjectDid]: {
uri: cur.uri,
cid: cur.cid,
name: cur.name,
purpose: cur.purpose,
avatar: cur.avatarCid

@ -26,8 +26,9 @@ import {
FeedGenInfoMap,
} from './types'
import { LabelService } from '../label'
import { FeedViews } from './views'
import { ActorService } from '../actor'
import { GraphService } from '../graph'
import { FeedViews } from './views'
export * from './types'
@ -42,6 +43,7 @@ export class FeedService {
services = {
label: LabelService.creator()(this.db),
actor: ActorService.creator(this.imgUriBuilder)(this.db),
graph: GraphService.creator(this.imgUriBuilder)(this.db),
}
selectPostQb() {
@ -297,17 +299,27 @@ export class FeedService {
const nestedFeedGenUris = nestedUris.filter(
(uri) => new AtUri(uri).collection === ids.AppBskyFeedGenerator,
)
const [postViews, actorViews, deepEmbedViews, labelViews, feedGenViews] =
await Promise.all([
this.getPostViews(nestedPostUris, requester),
this.getActorViews(nestedDids, requester, { skipLabels: true }),
this.embedsForPosts(nestedPostUris, requester, _depth + 1),
this.services.label.getLabelsForSubjects([
...nestedPostUris,
...nestedDids,
]),
this.getFeedGeneratorViews(nestedFeedGenUris, requester),
])
const nestedListUris = nestedUris.filter(
(uri) => new AtUri(uri).collection === ids.AppBskyGraphList,
)
const [
postViews,
actorViews,
deepEmbedViews,
labelViews,
feedGenViews,
listViews,
] = await Promise.all([
this.getPostViews(nestedPostUris, requester),
this.getActorViews(nestedDids, requester, { skipLabels: true }),
this.embedsForPosts(nestedPostUris, requester, _depth + 1),
this.services.label.getLabelsForSubjects([
...nestedPostUris,
...nestedDids,
]),
this.getFeedGeneratorViews(nestedFeedGenUris, requester),
this.services.graph.getListViews(nestedListUris, requester),
])
let embeds = images.reduce((acc, cur) => {
const embed = (acc[cur.postUri] ??= {
$type: 'app.bsky.embed.images#view',
@ -360,6 +372,16 @@ export class FeedService {
),
},
}
} else if (collection === ids.AppBskyGraphList && listViews[cur.uri]) {
recordEmbed = {
record: {
$type: 'app.bsky.graph.defs#listView',
...this.services.graph.formatListView(
listViews[cur.uri],
actorViews,
),
},
}
} else if (collection === ids.AppBskyFeedPost && postViews[cur.uri]) {
const formatted = this.views.formatPostView(
cur.uri,

@ -98,9 +98,24 @@ export class GraphService {
}
}
async getListViews(listUris: string[], requester: string) {
if (listUris.length < 1) return {}
const lists = await this.getListsQb(requester)
.where('list.uri', 'in', listUris)
.execute()
return lists.reduce(
(acc, cur) => ({
...acc,
[cur.uri]: cur,
}),
{},
)
}
formatListView(list: ListInfo, profiles: Record<string, ProfileView>) {
return {
uri: list.uri,
cid: list.cid,
creator: profiles[list.creator],
name: list.name,
purpose: list.purpose,

@ -4136,6 +4136,7 @@ export const schemaDict = {
'lex:app.bsky.embed.record#viewNotFound',
'lex:app.bsky.embed.record#viewBlocked',
'lex:app.bsky.feed.defs#generatorView',
'lex:app.bsky.graph.defs#listView',
],
},
},
@ -5370,12 +5371,16 @@ export const schemaDict = {
defs: {
listViewBasic: {
type: 'object',
required: ['uri', 'name', 'purpose'],
required: ['uri', 'cid', 'name', 'purpose'],
properties: {
uri: {
type: 'string',
format: 'at-uri',
},
cid: {
type: 'string',
format: 'cid',
},
name: {
type: 'string',
maxLength: 64,
@ -5400,12 +5405,16 @@ export const schemaDict = {
},
listView: {
type: 'object',
required: ['uri', 'creator', 'name', 'purpose', 'indexedAt'],
required: ['uri', 'cid', 'creator', 'name', 'purpose', 'indexedAt'],
properties: {
uri: {
type: 'string',
format: 'at-uri',
},
cid: {
type: 'string',
format: 'cid',
},
creator: {
type: 'ref',
ref: 'lex:app.bsky.actor.defs#profileView',

@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
import * as AppBskyFeedDefs from '../feed/defs'
import * as AppBskyGraphDefs from '../graph/defs'
import * as AppBskyActorDefs from '../actor/defs'
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
import * as AppBskyEmbedImages from './images'
@ -37,6 +38,7 @@ export interface View {
| ViewNotFound
| ViewBlocked
| AppBskyFeedDefs.GeneratorView
| AppBskyGraphDefs.ListView
| { $type: string; [k: string]: unknown }
[k: string]: unknown
}

@ -10,6 +10,7 @@ import * as AppBskyRichtextFacet from '../richtext/facet'
export interface ListViewBasic {
uri: string
cid: string
name: string
purpose: ListPurpose
avatar?: string
@ -32,6 +33,7 @@ export function validateListViewBasic(v: unknown): ValidationResult {
export interface ListView {
uri: string
cid: string
creator: AppBskyActorDefs.ProfileView
name: string
purpose: ListPurpose

@ -2030,6 +2030,7 @@ Object {
},
],
"list": Object {
"cid": "cids(1)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(4)/cids(0)@jpeg",
"description": "hi im bob label_me",
@ -2062,8 +2063,9 @@ Object {
"cursor": "0000000000000::bafycid",
"lists": Array [
Object {
"cid": "cids(0)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"description": "hi im bob label_me",
"did": "user(0)",
"displayName": "bobby",
@ -2087,8 +2089,9 @@ Object {
},
},
Object {
"cid": "cids(2)",
"creator": Object {
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(0)@jpeg",
"avatar": "https://bsky.public.url/image/sig()/rs:fill:1000:1000:1:0/plain/user(1)/cids(1)@jpeg",
"description": "hi im bob label_me",
"did": "user(0)",
"displayName": "bobby",

@ -1,5 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`pds views with mutes from mute lists embeds lists in posts 1`] = `
Object {
"author": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"did": "user(0)",
"displayName": "ali",
"handle": "alice.test",
"labels": Array [],
"viewer": Object {
"blockedBy": false,
"muted": false,
},
},
"cid": "cids(0)",
"embed": Object {
"$type": "app.bsky.embed.record#view",
"record": Object {
"$type": "app.bsky.graph.defs#listView",
"cid": "cids(2)",
"creator": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"did": "user(0)",
"displayName": "ali",
"handle": "alice.test",
"viewer": Object {
"blockedBy": false,
"muted": false,
},
},
"description": "new descript",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "updated alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
"uri": "record(1)",
"viewer": Object {
"muted": false,
},
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
"createdAt": "1970-01-01T00:00:00.000Z",
"embed": Object {
"$type": "app.bsky.embed.record",
"record": Object {
"cid": "cids(1)",
"uri": "record(1)",
},
},
"text": "list embed!",
},
"replyCount": 0,
"repostCount": 0,
"uri": "record(0)",
"viewer": Object {},
}
`;
exports[`pds views with mutes from mute lists flags mutes in threads 1`] = `
Object {
"$type": "app.bsky.feed.defs#threadViewPost",
@ -48,6 +109,7 @@ Object {
"muted": true,
"mutedByList": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"cid": "cids(2)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
@ -98,6 +160,7 @@ Object {
"muted": true,
"mutedByList": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"cid": "cids(2)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
@ -108,7 +171,7 @@ Object {
},
},
},
"cid": "cids(2)",
"cid": "cids(3)",
"embed": Object {
"$type": "app.bsky.embed.images#view",
"images": Array [
@ -122,7 +185,7 @@ Object {
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cid": "cids(3)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
@ -130,7 +193,7 @@ Object {
"val": "test-label",
},
Object {
"cid": "cids(2)",
"cid": "cids(3)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
@ -151,7 +214,7 @@ Object {
"$type": "blob",
"mimeType": "image/jpeg",
"ref": Object {
"$link": "cids(3)",
"$link": "cids(4)",
},
"size": 4114,
},
@ -185,6 +248,7 @@ Object {
"cursor": "0000000000000::bafycid",
"lists": Array [
Object {
"cid": "cids(0)",
"creator": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"description": "its me!",
@ -210,6 +274,7 @@ Object {
},
Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"cid": "cids(1)",
"creator": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"description": "its me!",
@ -242,6 +307,7 @@ Object {
"cursor": "0000000000000::bafycid",
"lists": Array [
Object {
"cid": "cids(0)",
"creator": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"description": "its me!",
@ -267,6 +333,7 @@ Object {
},
Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"cid": "cids(1)",
"creator": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"description": "its me!",
@ -309,6 +376,7 @@ Object {
"muted": true,
"mutedByList": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
@ -335,6 +403,7 @@ Object {
"muted": true,
"mutedByList": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"name": "alice mutes",
"purpose": "app.bsky.graph.defs#modlist",
@ -349,6 +418,7 @@ Object {
],
"list": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"cid": "cids(0)",
"creator": Object {
"avatar": "https://pds.public.url/image/KzkHFsMRQ6oAKCHCRKFA1H-rDdc7VOtvEVpUJ82TwyQ/rs:fill:1000:1000:1:0/plain/bafkreiaivizp4xldojmmpuzmiu75cmea7nq56dnntnuhzhsjcb63aou5ei@jpeg",
"description": "its me!",

@ -2,6 +2,7 @@ import AtpAgent, { AtUri } from '@atproto/api'
import { runTestServer, CloseFn, TestServerInfo, forSnapshot } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
import { RecordRef } from '../seeds/client'
describe('pds views with mutes from mute lists', () => {
let server: TestServerInfo
@ -37,6 +38,7 @@ describe('pds views with mutes from mute lists', () => {
})
let listUri: string
let listCid: string
it('creates a list with some items', async () => {
const avatar = await sc.uploadFile(
@ -57,6 +59,7 @@ describe('pds views with mutes from mute lists', () => {
sc.getHeaders(alice),
)
listUri = list.uri
listCid = list.cid
await agent.api.app.bsky.graph.listitem.create(
{ repo: alice },
{
@ -333,4 +336,20 @@ describe('pds views with mutes from mute lists', () => {
expect(got.data.list.avatar).toBeUndefined()
expect(got.data.items.length).toBe(2)
})
it('embeds lists in posts', async () => {
const postRef = await sc.post(
alice,
'list embed!',
undefined,
undefined,
new RecordRef(listUri, listCid),
)
const res = await agent.api.app.bsky.feed.getPosts(
{ uris: [postRef.ref.uriStr] },
{ headers: sc.getHeaders(alice) },
)
expect(res.data.posts.length).toBe(1)
expect(forSnapshot(res.data.posts[0])).toMatchSnapshot()
})
})